diff options
Diffstat (limited to 'src')
349 files changed, 20262 insertions, 5708 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d132ab969..f49a31612 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -31,17 +31,15 @@ add_library(common STATIC bit_set.h break_points.cpp break_points.h - chunk_file.h - code_block.h + cityhash.cpp + cityhash.h color.h common_funcs.h common_paths.h common_types.h file_util.cpp file_util.h - hash.cpp hash.h - linear_disk_cache.h logging/backend.cpp logging/backend.h logging/filter.cpp @@ -58,7 +56,6 @@ add_library(common STATIC misc.cpp param_package.cpp param_package.h - platform.h quaternion.h scm_rev.cpp scm_rev.h @@ -90,7 +87,7 @@ endif() create_target_directory_groups(common) -target_link_libraries(common PUBLIC Boost::boost microprofile) +target_link_libraries(common PUBLIC Boost::boost fmt microprofile) if (ARCHITECTURE_x86_64) target_link_libraries(common PRIVATE xbyak) endif() diff --git a/src/common/assert.h b/src/common/assert.h index 655446f34..3ee07f6a2 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -30,14 +30,15 @@ __declspec(noinline, noreturn) #define ASSERT(_a_) \ do \ if (!(_a_)) { \ - assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \ + assert_noinline_call([] { NGLOG_CRITICAL(Debug, "Assertion Failed!"); }); \ } \ while (0) #define ASSERT_MSG(_a_, ...) \ do \ if (!(_a_)) { \ - assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \ + assert_noinline_call( \ + [&] { NGLOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \ } \ while (0) diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 0cc0a1be0..65e357dec 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -115,7 +115,7 @@ private: // assignment would copy the full storage value, rather than just the bits // relevant to this particular bit field. // We don't delete it because we want BitField to be trivially copyable. - BitField& operator=(const BitField&) = default; + constexpr BitField& operator=(const BitField&) = default; // StorageType is T for non-enum types and the underlying type of T if // T is an enumeration. Note that T is wrapped within an enable_if in the @@ -166,20 +166,20 @@ public: // so that we can use this within unions constexpr BitField() = default; - FORCE_INLINE operator T() const { + constexpr FORCE_INLINE operator T() const { return Value(); } - FORCE_INLINE void Assign(const T& value) { + constexpr FORCE_INLINE void Assign(const T& value) { storage = (storage & ~mask) | FormatValue(value); } - FORCE_INLINE T Value() const { + constexpr T Value() const { return ExtractValue(storage); } // TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015 - FORCE_INLINE bool ToBool() const { + constexpr FORCE_INLINE bool ToBool() const { return Value() != 0; } @@ -192,11 +192,6 @@ private: static_assert(position < 8 * sizeof(T), "Invalid position"); static_assert(bits <= 8 * sizeof(T), "Invalid number of bits"); static_assert(bits > 0, "Invalid number of bits"); - static_assert(std::is_pod<T>::value, "Invalid base type"); + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField"); }; #pragma pack() - -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) -static_assert(std::is_trivially_copyable<BitField<0, 1, unsigned>>::value, - "BitField must be trivially copyable"); -#endif diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h deleted file mode 100644 index 972ef9039..000000000 --- a/src/common/chunk_file.h +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#pragma once - -// Extremely simple serialization framework. - -// (mis)-features: -// + Super fast -// + Very simple -// + Same code is used for serialization and deserializaition (in most cases) -// - Zero backwards/forwards compatibility -// - Serialization code for anything complex has to be manually written. - -#include <cstring> -#include <deque> -#include <list> -#include <map> -#include <set> -#include <string> -#include <type_traits> -#include <utility> -#include <vector> -#include "common/assert.h" -#include "common/common_types.h" -#include "common/logging/log.h" - -template <class T> -struct LinkedListItem : public T { - LinkedListItem<T>* next; -}; - -class PointerWrap; - -class PointerWrapSection { -public: - PointerWrapSection(PointerWrap& p, int ver, const char* title) - : p_(p), ver_(ver), title_(title) {} - ~PointerWrapSection(); - - bool operator==(const int& v) const { - return ver_ == v; - } - bool operator!=(const int& v) const { - return ver_ != v; - } - bool operator<=(const int& v) const { - return ver_ <= v; - } - bool operator>=(const int& v) const { - return ver_ >= v; - } - bool operator<(const int& v) const { - return ver_ < v; - } - bool operator>(const int& v) const { - return ver_ > v; - } - - operator bool() const { - return ver_ > 0; - } - -private: - PointerWrap& p_; - int ver_; - const char* title_; -}; - -// Wrapper class -class PointerWrap { -// This makes it a compile error if you forget to define DoState() on non-POD. -// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason... -#ifdef _MSC_VER - template <typename T, bool isPOD = std::is_pod<T>::value, - bool isPointer = std::is_pointer<T>::value> -#else - template <typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value> -#endif - struct DoHelper { - static void DoArray(PointerWrap* p, T* x, int count) { - for (int i = 0; i < count; ++i) - p->Do(x[i]); - } - - static void Do(PointerWrap* p, T& x) { - p->DoClass(x); - } - }; - - template <typename T> - struct DoHelper<T, true, false> { - static void DoArray(PointerWrap* p, T* x, int count) { - p->DoVoid((void*)x, sizeof(T) * count); - } - - static void Do(PointerWrap* p, T& x) { - p->DoVoid((void*)&x, sizeof(x)); - } - }; - -public: - enum Mode { - MODE_READ = 1, // load - MODE_WRITE, // save - MODE_MEASURE, // calculate size - MODE_VERIFY, // compare - }; - - enum Error { - ERROR_NONE = 0, - ERROR_WARNING = 1, - ERROR_FAILURE = 2, - }; - - u8** ptr; - Mode mode; - Error error; - -public: - PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {} - PointerWrap(unsigned char** ptr_, int mode_) - : ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {} - - PointerWrapSection Section(const char* title, int ver) { - return Section(title, ver, ver); - } - - // The returned object can be compared against the version that was loaded. - // This can be used to support versions as old as minVer. - // Version = 0 means the section was not found. - PointerWrapSection Section(const char* title, int minVer, int ver) { - char marker[16] = {0}; - int foundVersion = ver; - - strncpy(marker, title, sizeof(marker)); - if (!ExpectVoid(marker, sizeof(marker))) { - // Might be before we added name markers for safety. - if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion))) - DoMarker(title); - // Wasn't found, but maybe we can still load the state. - else - foundVersion = 0; - } else - Do(foundVersion); - - if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) { - LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion, - title); - SetError(ERROR_FAILURE); - return PointerWrapSection(*this, -1, title); - } - return PointerWrapSection(*this, foundVersion, title); - } - - void SetMode(Mode mode_) { - mode = mode_; - } - Mode GetMode() const { - return mode; - } - u8** GetPPtr() { - return ptr; - } - void SetError(Error error_) { - if (error < error_) - error = error_; - if (error > ERROR_WARNING) - mode = PointerWrap::MODE_MEASURE; - } - - bool ExpectVoid(void* data, int size) { - switch (mode) { - case MODE_READ: - if (memcmp(data, *ptr, size) != 0) - return false; - break; - case MODE_WRITE: - memcpy(*ptr, data, size); - break; - case MODE_MEASURE: - break; // MODE_MEASURE - don't need to do anything - case MODE_VERIFY: - for (int i = 0; i < size; i++) { - DEBUG_ASSERT_MSG( - ((u8*)data)[i] == (*ptr)[i], - "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", - ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], - &(*ptr)[i]); - } - break; - default: - break; // throw an error? - } - (*ptr) += size; - return true; - } - - void DoVoid(void* data, int size) { - switch (mode) { - case MODE_READ: - memcpy(data, *ptr, size); - break; - case MODE_WRITE: - memcpy(*ptr, data, size); - break; - case MODE_MEASURE: - break; // MODE_MEASURE - don't need to do anything - case MODE_VERIFY: - for (int i = 0; i < size; i++) { - DEBUG_ASSERT_MSG( - ((u8*)data)[i] == (*ptr)[i], - "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", - ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], - &(*ptr)[i]); - } - break; - default: - break; // throw an error? - } - (*ptr) += size; - } - - template <class K, class T> - void Do(std::map<K, T*>& x) { - if (mode == MODE_READ) { - for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (it->second != nullptr) - delete it->second; - } - } - T* dv = nullptr; - DoMap(x, dv); - } - - template <class K, class T> - void Do(std::map<K, T>& x) { - T dv = T(); - DoMap(x, dv); - } - - template <class K, class T> - void DoMap(std::map<K, T>& x, T& default_val) { - unsigned int number = (unsigned int)x.size(); - Do(number); - switch (mode) { - case MODE_READ: { - x.clear(); - while (number > 0) { - K first = K(); - Do(first); - T second = default_val; - Do(second); - x[first] = second; - --number; - } - } break; - case MODE_WRITE: - case MODE_MEASURE: - case MODE_VERIFY: { - typename std::map<K, T>::iterator itr = x.begin(); - while (number > 0) { - K first = itr->first; - Do(first); - Do(itr->second); - --number; - ++itr; - } - } break; - } - } - - template <class K, class T> - void Do(std::multimap<K, T*>& x) { - if (mode == MODE_READ) { - for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (it->second != nullptr) - delete it->second; - } - } - T* dv = nullptr; - DoMultimap(x, dv); - } - - template <class K, class T> - void Do(std::multimap<K, T>& x) { - T dv = T(); - DoMultimap(x, dv); - } - - template <class K, class T> - void DoMultimap(std::multimap<K, T>& x, T& default_val) { - unsigned int number = (unsigned int)x.size(); - Do(number); - switch (mode) { - case MODE_READ: { - x.clear(); - while (number > 0) { - K first = K(); - Do(first); - T second = default_val; - Do(second); - x.insert(std::make_pair(first, second)); - --number; - } - } break; - case MODE_WRITE: - case MODE_MEASURE: - case MODE_VERIFY: { - typename std::multimap<K, T>::iterator itr = x.begin(); - while (number > 0) { - Do(itr->first); - Do(itr->second); - --number; - ++itr; - } - } break; - } - } - - // Store vectors. - template <class T> - void Do(std::vector<T*>& x) { - T* dv = nullptr; - DoVector(x, dv); - } - - template <class T> - void Do(std::vector<T>& x) { - T dv = T(); - DoVector(x, dv); - } - - template <class T> - void DoPOD(std::vector<T>& x) { - T dv = T(); - DoVectorPOD(x, dv); - } - - template <class T> - void Do(std::vector<T>& x, T& default_val) { - DoVector(x, default_val); - } - - template <class T> - void DoVector(std::vector<T>& x, T& default_val) { - u32 vec_size = (u32)x.size(); - Do(vec_size); - x.resize(vec_size, default_val); - if (vec_size > 0) - DoArray(&x[0], vec_size); - } - - template <class T> - void DoVectorPOD(std::vector<T>& x, T& default_val) { - u32 vec_size = (u32)x.size(); - Do(vec_size); - x.resize(vec_size, default_val); - if (vec_size > 0) - DoArray(&x[0], vec_size); - } - - // Store deques. - template <class T> - void Do(std::deque<T*>& x) { - T* dv = nullptr; - DoDeque(x, dv); - } - - template <class T> - void Do(std::deque<T>& x) { - T dv = T(); - DoDeque(x, dv); - } - - template <class T> - void DoDeque(std::deque<T>& x, T& default_val) { - u32 deq_size = (u32)x.size(); - Do(deq_size); - x.resize(deq_size, default_val); - u32 i; - for (i = 0; i < deq_size; i++) - Do(x[i]); - } - - // Store STL lists. - template <class T> - void Do(std::list<T*>& x) { - T* dv = nullptr; - Do(x, dv); - } - - template <class T> - void Do(std::list<T>& x) { - T dv = T(); - DoList(x, dv); - } - - template <class T> - void Do(std::list<T>& x, T& default_val) { - DoList(x, default_val); - } - - template <class T> - void DoList(std::list<T>& x, T& default_val) { - u32 list_size = (u32)x.size(); - Do(list_size); - x.resize(list_size, default_val); - - typename std::list<T>::iterator itr, end; - for (itr = x.begin(), end = x.end(); itr != end; ++itr) - Do(*itr); - } - - // Store STL sets. - template <class T> - void Do(std::set<T*>& x) { - if (mode == MODE_READ) { - for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (*it != nullptr) - delete *it; - } - } - DoSet(x); - } - - template <class T> - void Do(std::set<T>& x) { - DoSet(x); - } - - template <class T> - void DoSet(std::set<T>& x) { - unsigned int number = (unsigned int)x.size(); - Do(number); - - switch (mode) { - case MODE_READ: { - x.clear(); - while (number-- > 0) { - T it = T(); - Do(it); - x.insert(it); - } - } break; - case MODE_WRITE: - case MODE_MEASURE: - case MODE_VERIFY: { - typename std::set<T>::iterator itr = x.begin(); - while (number-- > 0) - Do(*itr++); - } break; - - default: - LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode); - } - } - - // Store strings. - void Do(std::string& x) { - int stringLen = (int)x.length() + 1; - Do(stringLen); - - switch (mode) { - case MODE_READ: - x = (char*)*ptr; - break; - case MODE_WRITE: - memcpy(*ptr, x.c_str(), stringLen); - break; - case MODE_MEASURE: - break; - case MODE_VERIFY: - DEBUG_ASSERT_MSG((x == (char*)*ptr), - "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", - x.c_str(), (char*)*ptr, ptr); - break; - } - (*ptr) += stringLen; - } - - void Do(std::wstring& x) { - int stringLen = sizeof(wchar_t) * ((int)x.length() + 1); - Do(stringLen); - - switch (mode) { - case MODE_READ: - x = (wchar_t*)*ptr; - break; - case MODE_WRITE: - memcpy(*ptr, x.c_str(), stringLen); - break; - case MODE_MEASURE: - break; - case MODE_VERIFY: - DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr), - "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", - x.c_str(), (wchar_t*)*ptr, ptr); - break; - } - (*ptr) += stringLen; - } - - template <class T> - void DoClass(T& x) { - x.DoState(*this); - } - - template <class T> - void DoClass(T*& x) { - if (mode == MODE_READ) { - if (x != nullptr) - delete x; - x = new T(); - } - x->DoState(*this); - } - - template <class T> - void DoArray(T* x, int count) { - DoHelper<T>::DoArray(this, x, count); - } - - template <class T> - void Do(T& x) { - DoHelper<T>::Do(this, x); - } - - template <class T> - void DoPOD(T& x) { - DoHelper<T>::Do(this, x); - } - - template <class T> - void DoPointer(T*& x, T* const base) { - // pointers can be more than 2^31 apart, but you're using this function wrong if you need - // that much range - s32 offset = x - base; - Do(offset); - if (mode == MODE_READ) - x = base + offset; - } - - template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*), - void (*TDo)(PointerWrap&, T*)> - void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr) { - LinkedListItem<T>* list_cur = list_start; - LinkedListItem<T>* prev = nullptr; - - while (true) { - u8 shouldExist = (list_cur ? 1 : 0); - Do(shouldExist); - if (shouldExist == 1) { - LinkedListItem<T>* cur = list_cur ? list_cur : TNew(); - TDo(*this, (T*)cur); - if (!list_cur) { - if (mode == MODE_READ) { - cur->next = nullptr; - list_cur = cur; - if (prev) - prev->next = cur; - else - list_start = cur; - } else { - TFree(cur); - continue; - } - } - } else { - if (mode == MODE_READ) { - if (prev) - prev->next = nullptr; - if (list_end) - *list_end = prev; - if (list_cur) { - if (list_start == list_cur) - list_start = nullptr; - do { - LinkedListItem<T>* next = list_cur->next; - TFree(list_cur); - list_cur = next; - } while (list_cur); - } - } - break; - } - prev = list_cur; - list_cur = list_cur->next; - } - } - - void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) { - u32 cookie = arbitraryNumber; - Do(cookie); - if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { - LOG_ERROR(Common, - "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). " - "Aborting savestate load...", - prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); - SetError(ERROR_FAILURE); - } - } -}; - -inline PointerWrapSection::~PointerWrapSection() { - if (ver_ > 0) { - p_.DoMarker(title_); - } -} diff --git a/src/common/cityhash.cpp b/src/common/cityhash.cpp new file mode 100644 index 000000000..de31ffbd8 --- /dev/null +++ b/src/common/cityhash.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides CityHash64() and related functions. +// +// It's probably possible to create even faster hash functions by +// writing a program that systematically explores some of the space of +// possible hash functions, by using SIMD instructions, or by +// compromising on hash quality. + +#include <algorithm> +#include <string.h> // for memcpy and memset +#include "cityhash.h" +#include "common/swap.h" + +// #include "config.h" +#ifdef __GNUC__ +#define HAVE_BUILTIN_EXPECT 1 +#endif +#ifdef COMMON_BIG_ENDIAN +#define WORDS_BIGENDIAN 1 +#endif + +using namespace std; + +typedef uint8_t uint8; +typedef uint32_t uint32; +typedef uint64_t uint64; + +namespace Common { + +static uint64 UNALIGNED_LOAD64(const char* p) { + uint64 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +static uint32 UNALIGNED_LOAD32(const char* p) { + uint32 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +#ifdef WORDS_BIGENDIAN +#define uint32_in_expected_order(x) (swap32(x)) +#define uint64_in_expected_order(x) (swap64(x)) +#else +#define uint32_in_expected_order(x) (x) +#define uint64_in_expected_order(x) (x) +#endif + +#if !defined(LIKELY) +#if HAVE_BUILTIN_EXPECT +#define LIKELY(x) (__builtin_expect(!!(x), 1)) +#else +#define LIKELY(x) (x) +#endif +#endif + +static uint64 Fetch64(const char* p) { + return uint64_in_expected_order(UNALIGNED_LOAD64(p)); +} + +static uint32 Fetch32(const char* p) { + return uint32_in_expected_order(UNALIGNED_LOAD32(p)); +} + +// Some primes between 2^63 and 2^64 for various uses. +static const uint64 k0 = 0xc3a5c85c97cb3127ULL; +static const uint64 k1 = 0xb492b66fbe98f273ULL; +static const uint64 k2 = 0x9ae16a3b2f90404fULL; + +// Bitwise right rotate. Normally this will compile to a single +// instruction, especially if the shift is a manifest constant. +static uint64 Rotate(uint64 val, int shift) { + // Avoid shifting by 64: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); +} + +static uint64 ShiftMix(uint64 val) { + return val ^ (val >> 47); +} + +static uint64 HashLen16(uint64 u, uint64 v) { + return Hash128to64(uint128(u, v)); +} + +static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) { + // Murmur-inspired hashing. + uint64 a = (u ^ v) * mul; + a ^= (a >> 47); + uint64 b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; +} + +static uint64 HashLen0to16(const char* s, size_t len) { + if (len >= 8) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) + k2; + uint64 b = Fetch64(s + len - 8); + uint64 c = Rotate(b, 37) * mul + a; + uint64 d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch32(s); + return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); + } + if (len > 0) { + uint8 a = s[0]; + uint8 b = s[len >> 1]; + uint8 c = s[len - 1]; + uint32 y = static_cast<uint32>(a) + (static_cast<uint32>(b) << 8); + uint32 z = static_cast<uint32>(len) + (static_cast<uint32>(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; +} + +// This probably works well for 16-byte strings as well, but it may be overkill +// in that case. +static uint64 HashLen17to32(const char* s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k1; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 8) * mul; + uint64 d = Fetch64(s + len - 16) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul); +} + +// Return a 16-byte hash for 48 bytes. Quick and dirty. +// Callers do best to use "random-looking" values for a and b. +static pair<uint64, uint64> WeakHashLen32WithSeeds(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a, + uint64 b) { + a += w; + b = Rotate(b + a + z, 21); + uint64 c = a; + a += x; + a += y; + b += Rotate(a, 44); + return make_pair(a + z, b + c); +} + +// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. +static pair<uint64, uint64> WeakHashLen32WithSeeds(const char* s, uint64 a, uint64 b) { + return WeakHashLen32WithSeeds(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a, + b); +} + +// Return an 8-byte hash for 33 to 64 bytes. +static uint64 HashLen33to64(const char* s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k2; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 24); + uint64 d = Fetch64(s + len - 32); + uint64 e = Fetch64(s + 16) * k2; + uint64 f = Fetch64(s + 24) * 9; + uint64 g = Fetch64(s + len - 8); + uint64 h = Fetch64(s + len - 16) * mul; + uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + uint64 v = ((a + g) ^ d) + f + 1; + uint64 w = swap64((u + v) * mul) + h; + uint64 x = Rotate(e + f, 42) + c; + uint64 y = (swap64((v + w) * mul) + g) * mul; + uint64 z = e + f + c; + a = swap64((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + return b + x; +} + +uint64 CityHash64(const char* s, size_t len) { + if (len <= 32) { + if (len <= 16) { + return HashLen0to16(s, len); + } else { + return HashLen17to32(s, len); + } + } else if (len <= 64) { + return HashLen33to64(s, len); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + uint64 x = Fetch64(s + len - 40); + uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56); + uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); + pair<uint64, uint64> v = WeakHashLen32WithSeeds(s + len - 64, len, z); + pair<uint64, uint64> w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); + x = x * k1 + Fetch64(s); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast<size_t>(63); + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 64; + } while (len != 0); + return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, + HashLen16(v.second, w.second) + x); +} + +uint64 CityHash64WithSeed(const char* s, size_t len, uint64 seed) { + return CityHash64WithSeeds(s, len, k2, seed); +} + +uint64 CityHash64WithSeeds(const char* s, size_t len, uint64 seed0, uint64 seed1) { + return HashLen16(CityHash64(s, len) - seed0, seed1); +} + +// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings +// of any length representable in signed long. Based on City and Murmur. +static uint128 CityMurmur(const char* s, size_t len, uint128 seed) { + uint64 a = Uint128Low64(seed); + uint64 b = Uint128High64(seed); + uint64 c = 0; + uint64 d = 0; + signed long l = static_cast<long>(len) - 16; + if (l <= 0) { // len <= 16 + a = ShiftMix(a * k1) * k1; + c = b * k1 + HashLen0to16(s, len); + d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); + } else { // len > 16 + c = HashLen16(Fetch64(s + len - 8) + k1, a); + d = HashLen16(b + len, c + Fetch64(s + len - 16)); + a += d; + do { + a ^= ShiftMix(Fetch64(s) * k1) * k1; + a *= k1; + b ^= a; + c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; + c *= k1; + d ^= c; + s += 16; + l -= 16; + } while (l > 0); + } + a = HashLen16(a, c); + b = HashLen16(d, b); + return uint128(a ^ b, HashLen16(b, a)); +} + +uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) { + if (len < 128) { + return CityMurmur(s, len, seed); + } + + // We expect len >= 128 to be the common case. Keep 56 bytes of state: + // v, w, x, y, and z. + pair<uint64, uint64> v, w; + uint64 x = Uint128Low64(seed); + uint64 y = Uint128High64(seed); + uint64 z = len * k1; + v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); + v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); + w.first = Rotate(y + z, 35) * k1 + x; + w.second = Rotate(x + Fetch64(s + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 128; + } while (LIKELY(len >= 128)); + x += Rotate(v.first + z, 49) * k0; + y = y * k0 + Rotate(w.second, 37); + z = z * k0 + Rotate(w.first, 27); + w.first *= 9; + v.first *= k0; + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (size_t tail_done = 0; tail_done < len;) { + tail_done += 32; + y = Rotate(x + y, 42) * k0 + v.second; + w.first += Fetch64(s + len - tail_done + 16); + x = x * k0 + w.first; + z += w.second + Fetch64(s + len - tail_done); + w.second += v.first; + v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); + v.first *= k0; + } + // At this point our 56 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 56-byte-to-8-byte hashes to get a 16-byte final result. + x = HashLen16(x, v.first); + y = HashLen16(y + z, w.first); + return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second)); +} + +uint128 CityHash128(const char* s, size_t len) { + return len >= 16 + ? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0)) + : CityHash128WithSeed(s, len, uint128(k0, k1)); +} + +} // namespace Common diff --git a/src/common/cityhash.h b/src/common/cityhash.h new file mode 100644 index 000000000..bcebdb150 --- /dev/null +++ b/src/common/cityhash.h @@ -0,0 +1,110 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// http://code.google.com/p/cityhash/ +// +// This file provides a few functions for hashing strings. All of them are +// high-quality functions in the sense that they pass standard tests such +// as Austin Appleby's SMHasher. They are also fast. +// +// For 64-bit x86 code, on short strings, we don't know of anything faster than +// CityHash64 that is of comparable quality. We believe our nearest competitor +// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash +// tables and most other hashing (excluding cryptography). +// +// For 64-bit x86 code, on long strings, the picture is more complicated. +// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc., +// CityHashCrc128 appears to be faster than all competitors of comparable +// quality. CityHash128 is also good but not quite as fast. We believe our +// nearest competitor is Bob Jenkins' Spooky. We don't have great data for +// other 64-bit CPUs, but for long strings we know that Spooky is slightly +// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example. +// Note that CityHashCrc128 is declared in citycrc.h. +// +// For 32-bit x86 code, we don't know of anything faster than CityHash32 that +// is of comparable quality. We believe our nearest competitor is Murmur3A. +// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.) +// +// Functions in the CityHash family are not suitable for cryptography. +// +// Please see CityHash's README file for more details on our performance +// measurements and so on. +// +// WARNING: This code has been only lightly tested on big-endian platforms! +// It is known to work well on little-endian platforms that have a small penalty +// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. +// It should work on all 32-bit and 64-bit platforms that allow unaligned reads; +// bug reports are welcome. +// +// By the way, for some hash functions, given strings a and b, the hash +// of a+b is easily derived from the hashes of a and b. This property +// doesn't hold for any hash functions in this file. + +#pragma once + +#include <utility> +#include <stdint.h> +#include <stdlib.h> // for size_t. + +namespace Common { + +typedef std::pair<uint64_t, uint64_t> uint128; + +inline uint64_t Uint128Low64(const uint128& x) { + return x.first; +} +inline uint64_t Uint128High64(const uint128& x) { + return x.second; +} + +// Hash function for a byte array. +uint64_t CityHash64(const char* buf, size_t len); + +// Hash function for a byte array. For convenience, a 64-bit seed is also +// hashed into the result. +uint64_t CityHash64WithSeed(const char* buf, size_t len, uint64_t seed); + +// Hash function for a byte array. For convenience, two seeds are also +// hashed into the result. +uint64_t CityHash64WithSeeds(const char* buf, size_t len, uint64_t seed0, uint64_t seed1); + +// Hash function for a byte array. +uint128 CityHash128(const char* s, size_t len); + +// Hash function for a byte array. For convenience, a 128-bit seed is also +// hashed into the result. +uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed); + +// Hash 128 input bits down to 64 bits of output. +// This is intended to be a reasonably good hash function. +inline uint64_t Hash128to64(const uint128& x) { + // Murmur-inspired hashing. + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; + a ^= (a >> 47); + uint64_t b = (Uint128High64(x) ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; +} + +} // namespace Common diff --git a/src/common/code_block.h b/src/common/code_block.h deleted file mode 100644 index 6a55a8e30..000000000 --- a/src/common/code_block.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include "common/common_types.h" -#include "common/memory_util.h" - -// Everything that needs to generate code should inherit from this. -// You get memory management for free, plus, you can use all emitter functions without -// having to prefix them with gen-> or something similar. -// Example implementation: -// class JIT : public CodeBlock<ARMXEmitter> {} -template <class T> -class CodeBlock : public T, NonCopyable { -private: - // A privately used function to set the executable RAM space to something invalid. - // For debugging usefulness it should be used to set the RAM to a host specific breakpoint - // instruction - virtual void PoisonMemory() = 0; - -protected: - u8* region; - size_t region_size; - -public: - CodeBlock() : region(nullptr), region_size(0) {} - virtual ~CodeBlock() { - if (region) - FreeCodeSpace(); - } - - // Call this before you generate any code. - void AllocCodeSpace(int size) { - region_size = size; - region = (u8*)AllocateExecutableMemory(region_size); - T::SetCodePtr(region); - } - - // Always clear code space with breakpoints, so that if someone accidentally executes - // uninitialized, it just breaks into the debugger. - void ClearCodeSpace() { - PoisonMemory(); - ResetCodePtr(); - } - - // Call this when shutting down. Don't rely on the destructor, even though it'll do the job. - void FreeCodeSpace() { -#ifdef __SYMBIAN32__ - ResetExecutableMemory(region); -#else - FreeMemoryPages(region, region_size); -#endif - region = nullptr; - region_size = 0; - } - - bool IsInSpace(const u8* ptr) { - return (ptr >= region) && (ptr < (region + region_size)); - } - - // Cannot currently be undone. Will write protect the entire code region. - // Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()). - void WriteProtect() { - WriteProtectMemory(region, region_size, true); - } - - void ResetCodePtr() { - T::SetCodePtr(region); - } - - size_t GetSpaceLeft() const { - return region_size - (T::GetCodePtr() - region); - } - - u8* GetBasePtr() { - return region; - } - - size_t GetOffset(const u8* ptr) const { - return ptr - region; - } -}; diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 6f0604958..7cf7b7997 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -9,8 +9,6 @@ #endif #include "common/common_types.h" -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) - /// Textually concatenates two tokens. The double-expansion is required by the C preprocessor. #define CONCAT2(x, y) DO_CONCAT2(x, y) #define DO_CONCAT2(x, y) x##y @@ -74,11 +72,6 @@ inline u64 _rotr64(u64 x, unsigned int shift) { #else // _MSC_VER -#if (_MSC_VER < 1900) -// Function Cross-Compatibility -#define snprintf _snprintf -#endif - // Locale Cross-Compatibility #define locale_t _locale_t diff --git a/src/common/common_types.h b/src/common/common_types.h index 844d34965..6b1766dca 100644 --- a/src/common/common_types.h +++ b/src/common/common_types.h @@ -27,29 +27,23 @@ #include <array> #include <cstdint> -#ifdef _MSC_VER -#ifndef __func__ -#define __func__ __FUNCTION__ -#endif -#endif +using u8 = std::uint8_t; ///< 8-bit unsigned byte +using u16 = std::uint16_t; ///< 16-bit unsigned short +using u32 = std::uint32_t; ///< 32-bit unsigned word +using u64 = std::uint64_t; ///< 64-bit unsigned int -typedef std::uint8_t u8; ///< 8-bit unsigned byte -typedef std::uint16_t u16; ///< 16-bit unsigned short -typedef std::uint32_t u32; ///< 32-bit unsigned word -typedef std::uint64_t u64; ///< 64-bit unsigned int +using s8 = std::int8_t; ///< 8-bit signed byte +using s16 = std::int16_t; ///< 16-bit signed short +using s32 = std::int32_t; ///< 32-bit signed word +using s64 = std::int64_t; ///< 64-bit signed int -typedef std::int8_t s8; ///< 8-bit signed byte -typedef std::int16_t s16; ///< 16-bit signed short -typedef std::int32_t s32; ///< 32-bit signed word -typedef std::int64_t s64; ///< 64-bit signed int - -typedef float f32; ///< 32-bit floating point -typedef double f64; ///< 64-bit floating point +using f32 = float; ///< 32-bit floating point +using f64 = double; ///< 64-bit floating point // TODO: It would be nice to eventually replace these with strong types that prevent accidental // conversion between each other. -typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space. -typedef u64 PAddr; ///< Represents a pointer in the ARM11 physical address space. +using VAddr = u64; ///< Represents a pointer in the userspace virtual address space. +using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space. using u128 = std::array<std::uint64_t, 2>; static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide"); diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 4e1d702f7..2d0b81c6e 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -118,7 +118,7 @@ bool IsDirectory(const std::string& filename) { #endif if (result < 0) { - LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg()); + NGLOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg()); return false; } @@ -128,31 +128,29 @@ bool IsDirectory(const std::string& filename) { // Deletes a given filename, return true on success // Doesn't supports deleting a directory bool Delete(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "file %s", filename.c_str()); + NGLOG_TRACE(Common_Filesystem, "file {}", filename); // Return true because we care about the file no // being there, not the actual delete. if (!Exists(filename)) { - LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str()); + NGLOG_DEBUG(Common_Filesystem, "{} does not exist", filename); return true; } // We can't delete a directory if (IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str()); + NGLOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename); return false; } #ifdef _WIN32 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { - LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(), - GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg()); return false; } #else if (unlink(filename.c_str()) == -1) { - LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(), - GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); return false; } #endif @@ -162,16 +160,16 @@ bool Delete(const std::string& filename) { // Returns true if successful, or path already exists. bool CreateDir(const std::string& path) { - LOG_TRACE(Common_Filesystem, "directory %s", path.c_str()); + NGLOG_TRACE(Common_Filesystem, "directory {}", path); #ifdef _WIN32 if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr)) return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { - LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str()); + NGLOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path); return true; } - LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error); + NGLOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error); return false; #else if (mkdir(path.c_str(), 0755) == 0) @@ -180,11 +178,11 @@ bool CreateDir(const std::string& path) { int err = errno; if (err == EEXIST) { - LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); + NGLOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); return true; } - LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err)); + NGLOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); return false; #endif } @@ -192,10 +190,10 @@ bool CreateDir(const std::string& path) { // Creates the full path of fullPath returns true on success bool CreateFullPath(const std::string& fullPath) { int panicCounter = 100; - LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str()); + NGLOG_TRACE(Common_Filesystem, "path {}", fullPath); if (FileUtil::Exists(fullPath)) { - LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str()); + NGLOG_DEBUG(Common_Filesystem, "path exists {}", fullPath); return true; } @@ -211,14 +209,14 @@ bool CreateFullPath(const std::string& fullPath) { // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") std::string const subPath(fullPath.substr(0, position + 1)); if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) { - LOG_ERROR(Common, "CreateFullPath: directory creation failed"); + NGLOG_ERROR(Common, "CreateFullPath: directory creation failed"); return false; } // A safety check panicCounter--; if (panicCounter <= 0) { - LOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); + NGLOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); return false; } position++; @@ -227,11 +225,11 @@ bool CreateFullPath(const std::string& fullPath) { // Deletes a directory filename, returns true on success bool DeleteDir(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "directory %s", filename.c_str()); + NGLOG_TRACE(Common_Filesystem, "directory {}", filename); // check if a directory if (!FileUtil::IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str()); + NGLOG_ERROR(Common_Filesystem, "Not a directory {}", filename); return false; } @@ -242,14 +240,14 @@ bool DeleteDir(const std::string& filename) { if (rmdir(filename.c_str()) == 0) return true; #endif - LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); return false; } // renames file srcFilename to destFilename, returns true on success bool Rename(const std::string& srcFilename, const std::string& destFilename) { - LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); + NGLOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); #ifdef _WIN32 if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str()) == 0) @@ -258,21 +256,21 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) { if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) return true; #endif - LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), - GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, + GetLastErrorMsg()); return false; } // copies file srcFilename to destFilename, returns true on success bool Copy(const std::string& srcFilename, const std::string& destFilename) { - LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); + NGLOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); #ifdef _WIN32 if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(), Common::UTF8ToUTF16W(destFilename).c_str(), FALSE)) return true; - LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), - GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, + GetLastErrorMsg()); return false; #else @@ -284,8 +282,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { // Open input file FILE* input = fopen(srcFilename.c_str(), "rb"); if (!input) { - LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(), - destFilename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); return false; } @@ -293,8 +291,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { FILE* output = fopen(destFilename.c_str(), "wb"); if (!output) { fclose(input); - LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(), - destFilename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); return false; } @@ -304,8 +302,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { size_t rnum = fread(buffer, sizeof(char), BSIZE, input); if (rnum != BSIZE) { if (ferror(input) != 0) { - LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s", - srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", + srcFilename, destFilename, GetLastErrorMsg()); goto bail; } } @@ -313,8 +311,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { // write output size_t wnum = fwrite(buffer, sizeof(char), rnum, output); if (wnum != rnum) { - LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s", - srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); goto bail; } } @@ -334,12 +332,12 @@ bail: // Returns the size of filename (64bit) u64 GetSize(const std::string& filename) { if (!Exists(filename)) { - LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str()); + NGLOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); return 0; } if (IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str()); + NGLOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); return 0; } @@ -350,11 +348,11 @@ u64 GetSize(const std::string& filename) { if (stat(filename.c_str(), &buf) == 0) #endif { - LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size); + NGLOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size); return buf.st_size; } - LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg()); return 0; } @@ -362,7 +360,7 @@ u64 GetSize(const std::string& filename) { u64 GetSize(const int fd) { struct stat buf; if (fstat(fd, &buf) != 0) { - LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg()); return 0; } return buf.st_size; @@ -373,12 +371,14 @@ u64 GetSize(FILE* f) { // can't use off_t here because it can be 32-bit u64 pos = ftello(f); if (fseeko(f, 0, SEEK_END) != 0) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), + GetLastErrorMsg()); return 0; } u64 size = ftello(f); if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), + GetLastErrorMsg()); return 0; } return size; @@ -386,10 +386,10 @@ u64 GetSize(FILE* f) { // creates an empty file filename, returns true on success bool CreateEmptyFile(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "%s", filename.c_str()); + NGLOG_TRACE(Common_Filesystem, "{}", filename); if (!FileUtil::IOFile(filename, "wb")) { - LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); return false; } @@ -398,7 +398,7 @@ bool CreateEmptyFile(const std::string& filename) { bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory, DirectoryEntryCallable callback) { - LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str()); + NGLOG_TRACE(Common_Filesystem, "directory {}", directory); // How many files + directories we found unsigned found_entries = 0; @@ -556,7 +556,7 @@ std::string GetCurrentDir() { char* dir; if (!(dir = getcwd(nullptr, 0))) { #endif - LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg()); + NGLOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); return nullptr; } #ifdef _WIN32 @@ -653,12 +653,12 @@ static const std::string GetUserDirectory(const std::string& envvar) { else if (envvar == "XDG_CACHE_HOME") subdirectory = DIR_SEP ".cache"; else - ASSERT_MSG(false, "Unknown XDG variable %s.", envvar.c_str()); + ASSERT_MSG(false, "Unknown XDG variable {}.", envvar); user_dir = GetHomeDirectory() + subdirectory; } - ASSERT_MSG(!user_dir.empty(), "User directory %s musn’t be empty.", envvar.c_str()); - ASSERT_MSG(user_dir[0] == '/', "User directory %s must be absolute.", envvar.c_str()); + ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar); + ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar); return user_dir; } @@ -676,7 +676,7 @@ std::string GetSysDirectory() { #endif sysDir += DIR_SEP; - LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str()); + NGLOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir); return sysDir; } @@ -692,7 +692,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new if (!FileUtil::IsDirectory(paths[D_USER_IDX])) { paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; } else { - LOG_INFO(Common_Filesystem, "Using the local user directory"); + NGLOG_INFO(Common_Filesystem, "Using the local user directory"); } paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; @@ -719,7 +719,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new if (!newPath.empty()) { if (!FileUtil::IsDirectory(newPath)) { - LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str()); + NGLOG_ERROR(Common_Filesystem, "Invalid path specified {}", newPath); return paths[DirIDX]; } else { paths[DirIDX] = newPath; @@ -809,16 +809,16 @@ IOFile::~IOFile() { Close(); } -IOFile::IOFile(IOFile&& other) { +IOFile::IOFile(IOFile&& other) noexcept { Swap(other); } -IOFile& IOFile::operator=(IOFile&& other) { +IOFile& IOFile::operator=(IOFile&& other) noexcept { Swap(other); return *this; } -void IOFile::Swap(IOFile& other) { +void IOFile::Swap(IOFile& other) noexcept { std::swap(m_file, other.m_file); std::swap(m_good, other.m_good); } diff --git a/src/common/file_util.h b/src/common/file_util.h index 630232a25..fc6b3ea46 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path); // Set the current directory to given directory bool SetCurrentDir(const std::string& directory); -// Returns a pointer to a string with a Citra data dir in the user's home +// Returns a pointer to a string with a yuzu data dir in the user's home // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = ""); @@ -160,22 +160,18 @@ public: ~IOFile(); - IOFile(IOFile&& other); - IOFile& operator=(IOFile&& other); + IOFile(IOFile&& other) noexcept; + IOFile& operator=(IOFile&& other) noexcept; - void Swap(IOFile& other); + void Swap(IOFile& other) noexcept; bool Open(const std::string& filename, const char openmode[]); bool Close(); template <typename T> size_t ReadArray(T* data, size_t length) { - static_assert(std::is_standard_layout<T>(), - "Given array does not consist of standard layout objects"); -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); -#endif if (!IsOpen()) { m_good = false; @@ -191,12 +187,8 @@ public: template <typename T> size_t WriteArray(const T* data, size_t length) { - static_assert(std::is_standard_layout<T>(), - "Given array does not consist of standard layout objects"); -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) static_assert(std::is_trivially_copyable<T>(), "Given array does not consist of trivially copyable objects"); -#endif if (!IsOpen()) { m_good = false; @@ -210,11 +202,15 @@ public: return items_written; } - size_t ReadBytes(void* data, size_t length) { + template <typename T> + size_t ReadBytes(T* data, size_t length) { + static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); return ReadArray(reinterpret_cast<char*>(data), length); } - size_t WriteBytes(const void* data, size_t length) { + template <typename T> + size_t WriteBytes(const T* data, size_t length) { + static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); return WriteArray(reinterpret_cast<const char*>(data), length); } diff --git a/src/common/hash.cpp b/src/common/hash.cpp deleted file mode 100644 index a02e9e5b9..000000000 --- a/src/common/hash.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#if defined(_MSC_VER) -#include <stdlib.h> -#endif -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/hash.h" - -namespace Common { - -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -// Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do -// the conversion here -static FORCE_INLINE u64 getblock64(const u64* p, size_t i) { - return p[i]; -} - -// Finalization mix - force all bits of a hash block to avalanche -static FORCE_INLINE u64 fmix64(u64 k) { - k ^= k >> 33; - k *= 0xff51afd7ed558ccdllu; - k ^= k >> 33; - k *= 0xc4ceb9fe1a85ec53llu; - k ^= k >> 33; - - return k; -} - -// This is the 128-bit variant of the MurmurHash3 hash function that is targeted for 64-bit -// platforms (MurmurHash3_x64_128). It was taken from: -// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp -void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out) { - const u8* data = (const u8*)key; - const size_t nblocks = len / 16; - - u64 h1 = seed; - u64 h2 = seed; - - const u64 c1 = 0x87c37b91114253d5llu; - const u64 c2 = 0x4cf5ad432745937fllu; - - // Body - - const u64* blocks = (const u64*)(data); - - for (size_t i = 0; i < nblocks; i++) { - u64 k1 = getblock64(blocks, i * 2 + 0); - u64 k2 = getblock64(blocks, i * 2 + 1); - - k1 *= c1; - k1 = _rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - - h1 = _rotl64(h1, 27); - h1 += h2; - h1 = h1 * 5 + 0x52dce729; - - k2 *= c2; - k2 = _rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - - h2 = _rotl64(h2, 31); - h2 += h1; - h2 = h2 * 5 + 0x38495ab5; - } - - // Tail - - const u8* tail = (const u8*)(data + nblocks * 16); - - u64 k1 = 0; - u64 k2 = 0; - - switch (len & 15) { - case 15: - k2 ^= ((u64)tail[14]) << 48; - case 14: - k2 ^= ((u64)tail[13]) << 40; - case 13: - k2 ^= ((u64)tail[12]) << 32; - case 12: - k2 ^= ((u64)tail[11]) << 24; - case 11: - k2 ^= ((u64)tail[10]) << 16; - case 10: - k2 ^= ((u64)tail[9]) << 8; - case 9: - k2 ^= ((u64)tail[8]) << 0; - k2 *= c2; - k2 = _rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - - case 8: - k1 ^= ((u64)tail[7]) << 56; - case 7: - k1 ^= ((u64)tail[6]) << 48; - case 6: - k1 ^= ((u64)tail[5]) << 40; - case 5: - k1 ^= ((u64)tail[4]) << 32; - case 4: - k1 ^= ((u64)tail[3]) << 24; - case 3: - k1 ^= ((u64)tail[2]) << 16; - case 2: - k1 ^= ((u64)tail[1]) << 8; - case 1: - k1 ^= ((u64)tail[0]) << 0; - k1 *= c1; - k1 = _rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - }; - - // Finalization - - h1 ^= len; - h2 ^= len; - - h1 += h2; - h2 += h1; - - h1 = fmix64(h1); - h2 = fmix64(h2); - - h1 += h2; - h2 += h1; - - ((u64*)out)[0] = h1; - ((u64*)out)[1] = h2; -} - -} // namespace Common diff --git a/src/common/hash.h b/src/common/hash.h index ee2560dad..73c326980 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -5,12 +5,12 @@ #pragma once #include <cstddef> +#include <cstring> +#include "common/cityhash.h" #include "common/common_types.h" namespace Common { -void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out); - /** * Computes a 64-bit hash over the specified block of data * @param data Block of data to compute hash over @@ -18,9 +18,54 @@ void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out); * @returns 64-bit hash value that was computed over the data block */ static inline u64 ComputeHash64(const void* data, size_t len) { - u64 res[2]; - MurmurHash3_128(data, len, 0, res); - return res[0]; + return CityHash64(static_cast<const char*>(data), len); +} + +/** + * Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical + * that either the struct includes no padding, or that any padding is initialized to a known value + * by memsetting the struct to 0 before filling it in. + */ +template <typename T> +static inline u64 ComputeStructHash64(const T& data) { + static_assert(std::is_trivially_copyable<T>(), + "Type passed to ComputeStructHash64 must be trivially copyable"); + return ComputeHash64(&data, sizeof(data)); } +/// A helper template that ensures the padding in a struct is initialized by memsetting to 0. +template <typename T> +struct HashableStruct { + // In addition to being trivially copyable, T must also have a trivial default constructor, + // because any member initialization would be overridden by memset + static_assert(std::is_trivial<T>(), "Type passed to HashableStruct must be trivial"); + /* + * We use a union because "implicitly-defined copy/move constructor for a union X copies the + * object representation of X." and "implicitly-defined copy assignment operator for a union X + * copies the object representation (3.9) of X." = Bytewise copy instead of memberwise copy. + * This is important because the padding bytes are included in the hash and comparison between + * objects. + */ + union { + T state; + }; + + HashableStruct() { + // Memset structure to zero padding bits, so that they will be deterministic when hashing + std::memset(&state, 0, sizeof(T)); + } + + bool operator==(const HashableStruct<T>& o) const { + return std::memcmp(&state, &o.state, sizeof(T)) == 0; + }; + + bool operator!=(const HashableStruct<T>& o) const { + return !(*this == o); + }; + + size_t Hash() const { + return Common::ComputeStructHash64(state); + } +}; + } // namespace Common diff --git a/src/common/linear_disk_cache.h b/src/common/linear_disk_cache.h deleted file mode 100644 index 94c695163..000000000 --- a/src/common/linear_disk_cache.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <fstream> -#include "common/common_types.h" - -// defined in Version.cpp -extern const char* scm_rev_git_str; - -// On disk format: -// header{ -// u32 'DCAC'; -// u32 version; // svn_rev -// u16 sizeof(key_type); -// u16 sizeof(value_type); -//} - -// key_value_pair{ -// u32 value_size; -// key_type key; -// value_type[value_size] value; -//} - -template <typename K, typename V> -class LinearDiskCacheReader { -public: - virtual void Read(const K& key, const V* value, u32 value_size) = 0; -}; - -// Dead simple unsorted key-value store with append functionality. -// No random read functionality, all reading is done in OpenAndRead. -// Keys and values can contain any characters, including \0. -// -// Suitable for caching generated shader bytecode between executions. -// Not tuned for extreme performance but should be reasonably fast. -// Does not support keys or values larger than 2GB, which should be reasonable. -// Keys must have non-zero length; values can have zero length. - -// K and V are some POD type -// K : the key type -// V : value array type -template <typename K, typename V> -class LinearDiskCache { -public: - // return number of read entries - u32 OpenAndRead(const char* filename, LinearDiskCacheReader<K, V>& reader) { - using std::ios_base; - - // close any currently opened file - Close(); - m_num_entries = 0; - - // try opening for reading/writing - OpenFStream(m_file, filename, ios_base::in | ios_base::out | ios_base::binary); - - m_file.seekg(0, std::ios::end); - std::fstream::pos_type end_pos = m_file.tellg(); - m_file.seekg(0, std::ios::beg); - std::fstream::pos_type start_pos = m_file.tellg(); - std::streamoff file_size = end_pos - start_pos; - - if (m_file.is_open() && ValidateHeader()) { - // good header, read some key/value pairs - K key; - - V* value = nullptr; - u32 value_size; - u32 entry_number; - - std::fstream::pos_type last_pos = m_file.tellg(); - - while (Read(&value_size)) { - std::streamoff next_extent = - (last_pos - start_pos) + sizeof(value_size) + value_size; - if (next_extent > file_size) - break; - - delete[] value; - value = new V[value_size]; - - // read key/value and pass to reader - if (Read(&key) && Read(value, value_size) && Read(&entry_number) && - entry_number == m_num_entries + 1) { - reader.Read(key, value, value_size); - } else { - break; - } - - m_num_entries++; - last_pos = m_file.tellg(); - } - m_file.seekp(last_pos); - m_file.clear(); - - delete[] value; - return m_num_entries; - } - - // failed to open file for reading or bad header - // close and recreate file - Close(); - m_file.open(filename, ios_base::out | ios_base::trunc | ios_base::binary); - WriteHeader(); - return 0; - } - - void Sync() { - m_file.flush(); - } - - void Close() { - if (m_file.is_open()) - m_file.close(); - // clear any error flags - m_file.clear(); - } - - // Appends a key-value pair to the store. - void Append(const K& key, const V* value, u32 value_size) { - // TODO: Should do a check that we don't already have "key"? (I think each caller does that - // already.) - Write(&value_size); - Write(&key); - Write(value, value_size); - m_num_entries++; - Write(&m_num_entries); - } - -private: - void WriteHeader() { - Write(&m_header); - } - - bool ValidateHeader() { - char file_header[sizeof(Header)]; - - return (Read(file_header, sizeof(Header)) && - !memcmp((const char*)&m_header, file_header, sizeof(Header))); - } - - template <typename D> - bool Write(const D* data, u32 count = 1) { - return m_file.write((const char*)data, count * sizeof(D)).good(); - } - - template <typename D> - bool Read(const D* data, u32 count = 1) { - return m_file.read((char*)data, count * sizeof(D)).good(); - } - - struct Header { - Header() : id(*(u32*)"DCAC"), key_t_size(sizeof(K)), value_t_size(sizeof(V)) { - memcpy(ver, scm_rev_git_str, 40); - } - - const u32 id; - const u16 key_t_size, value_t_size; - char ver[40]; - - } m_header; - - std::fstream m_file; - u32 m_num_entries; -}; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 7f3ae1a4e..c26b20062 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -2,15 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> -#include <array> -#include <cstdio> +#include <utility> #include "common/assert.h" -#include "common/common_funcs.h" // snprintf compatibility define #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" +#include "common/string_util.h" namespace Log { @@ -37,16 +35,23 @@ namespace Log { SUB(Service, AM) \ SUB(Service, AOC) \ SUB(Service, APM) \ + SUB(Service, BCAT) \ + SUB(Service, Fatal) \ SUB(Service, Friend) \ SUB(Service, FS) \ SUB(Service, HID) \ SUB(Service, LM) \ + SUB(Service, MM) \ + SUB(Service, NFP) \ SUB(Service, NIFM) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ SUB(Service, PCTL) \ + SUB(Service, PREPO) \ SUB(Service, SET) \ SUB(Service, SM) \ + SUB(Service, SPL) \ + SUB(Service, SSL) \ SUB(Service, Time) \ SUB(Service, VI) \ CLS(HW) \ @@ -102,25 +107,20 @@ const char* GetLevelName(Level log_level) { } Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, - const char* function, const char* format, va_list args) { + const char* function, std::string message) { using std::chrono::duration_cast; using std::chrono::steady_clock; static steady_clock::time_point time_origin = steady_clock::now(); - std::array<char, 4 * 1024> formatting_buffer; - Entry entry; entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); entry.log_class = log_class; entry.log_level = log_level; - - snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, - line_nr); - entry.location = std::string(formatting_buffer.data()); - - vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); - entry.message = std::string(formatting_buffer.data()); + entry.filename = Common::TrimSourcePath(filename); + entry.line_num = line_nr; + entry.function = function; + entry.message = std::move(message); return entry; } @@ -131,15 +131,13 @@ void SetFilter(Filter* new_filter) { filter = new_filter; } -void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr, - const char* function, const char* format, ...) { - if (filter != nullptr && !filter->CheckMessage(log_class, log_level)) +void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, + unsigned int line_num, const char* function, const char* format, + const fmt::format_args& args) { + if (filter && !filter->CheckMessage(log_class, log_level)) return; - - va_list args; - va_start(args, format); - Entry entry = CreateEntry(log_class, log_level, filename, line_nr, function, format, args); - va_end(args); + Entry entry = + CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); PrintColoredMessage(entry); } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 70744e3e5..7e81efb23 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -22,13 +22,16 @@ struct Entry { std::chrono::microseconds timestamp; Class log_class; Level log_level; - std::string location; + std::string filename; + unsigned int line_num; + std::string function; std::string message; Entry() = default; Entry(Entry&& o) = default; Entry& operator=(Entry&& o) = default; + Entry& operator=(const Entry& o) = default; }; /** @@ -44,7 +47,7 @@ const char* GetLevelName(Level log_level); /// Creates a log entry by formatting the given source location, and message. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, - const char* function, const char* format, va_list args); + const char* function, std::string message); void SetFilter(Filter* filter); } // namespace Log diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 733247b51..428723dce 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -65,14 +65,14 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin, const std::string::const_iterator end) { auto level_separator = std::find(begin, end, ':'); if (level_separator == end) { - LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s", - std::string(begin, end).c_str()); + NGLOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s", + std::string(begin, end).c_str()); return false; } const Level level = GetLevelByName(level_separator + 1, end); if (level == Level::Count) { - LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str()); + NGLOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str()); return false; } @@ -83,7 +83,7 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin, const Class log_class = GetClassByName(begin, level_separator); if (log_class == Class::Count) { - LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); + NGLOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); return false; } diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index 16fa72642..ccca289bd 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -19,7 +19,7 @@ namespace Log { class Filter { public: /// Initializes the filter with all classes having `default_level` as the minimum level. - Filter(Level default_level); + Filter(Level default_level = Level::Info); /// Resets the filter so that all classes have `level` as the minimum displayed level. void ResetAll(Level level); diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 3cf13fcb0..c5015531c 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -4,6 +4,7 @@ #pragma once +#include <fmt/format.h> #include "common/common_types.h" namespace Log { @@ -54,16 +55,23 @@ enum class Class : ClassType { Service_AOC, ///< The AOC (AddOn Content) service Service_APM, ///< The APM (Performance) service Service_Audio, ///< The Audio (Audio control) service + Service_BCAT, ///< The BCAT service + Service_Fatal, ///< The Fatal service Service_Friend, ///< The friend service Service_FS, ///< The FS (Filesystem) service Service_HID, ///< The HID (Human interface device) service Service_LM, ///< The LM (Logger) service + Service_MM, ///< The MM (Multimedia) service + Service_NFP, ///< The NFP service Service_NIFM, ///< The NIFM (Network interface) service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service Service_PCTL, ///< The PCTL (Parental control) service + Service_PREPO, ///< The PREPO (Play report) service Service_SET, ///< The SET (Settings) service Service_SM, ///< The SM (Service manager) service + Service_SPL, ///< The SPL service + Service_SSL, ///< The SSL service Service_Time, ///< The time service Service_VI, ///< The VI (Video interface) service HW, ///< Low-level hardware emulation @@ -82,42 +90,44 @@ enum class Class : ClassType { Loader, ///< ROM loader Input, ///< Input emulation Network, ///< Network emulation - WebService, ///< Interface to Citra Web Services + WebService, ///< Interface to yuzu Web Services Count ///< Total number of logging classes }; -/// Logs a message to the global logger. -void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr, - const char* function, -#ifdef _MSC_VER - _Printf_format_string_ -#endif - const char* format, - ...) -#ifdef __GNUC__ - __attribute__((format(printf, 6, 7))) -#endif - ; +/// Logs a message to the global logger, using fmt +void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, + unsigned int line_num, const char* function, const char* format, + const fmt::format_args& args); -} // namespace Log +template <typename... Args> +void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, + const char* function, const char* format, const Args&... args) { + FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, + fmt::make_args(args...)); +} -#define LOG_GENERIC(log_class, log_level, ...) \ - ::Log::LogMessage(log_class, log_level, __FILE__, __LINE__, __func__, __VA_ARGS__) +} // namespace Log #ifdef _DEBUG -#define LOG_TRACE(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Trace, __VA_ARGS__) +#define NGLOG_TRACE(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) #else -#define LOG_TRACE(log_class, ...) (void(0)) +#define NGLOG_TRACE(log_class, fmt, ...) (void(0)) #endif -#define LOG_DEBUG(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Debug, __VA_ARGS__) -#define LOG_INFO(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Info, __VA_ARGS__) -#define LOG_WARNING(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Warning, __VA_ARGS__) -#define LOG_ERROR(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__) -#define LOG_CRITICAL(log_class, ...) \ - LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__) +#define NGLOG_DEBUG(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) +#define NGLOG_INFO(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) +#define NGLOG_WARNING(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) +#define NGLOG_ERROR(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) +#define NGLOG_CRITICAL(log_class, ...) \ + ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \ + __func__, __VA_ARGS__) diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index e7e46c76b..8583916a8 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -18,50 +18,29 @@ namespace Log { -// TODO(bunnei): This should be moved to a generic path manipulation library -const char* TrimSourcePath(const char* path, const char* root) { - const char* p = path; - - while (*p != '\0') { - const char* next_slash = p; - while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { - ++next_slash; - } - - bool is_src = Common::ComparePartialString(p, next_slash, root); - p = next_slash; - - if (*p != '\0') { - ++p; - } - if (is_src) { - path = p; - } - } - return path; -} - -void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) { +std::string FormatLogMessage(const Entry& entry) { unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); const char* class_name = GetLogClassName(entry.log_class); const char* level_name = GetLevelName(entry.log_level); - snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", time_seconds, time_fractional, - class_name, level_name, TrimSourcePath(entry.location.c_str()), entry.message.c_str()); + return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, + class_name, level_name, entry.filename, entry.function, entry.line_num, + entry.message); } void PrintMessage(const Entry& entry) { - std::array<char, 4 * 1024> format_buffer; - FormatLogMessage(entry, format_buffer.data(), format_buffer.size()); - fputs(format_buffer.data(), stderr); - fputc('\n', stderr); + auto str = FormatLogMessage(entry) + '\n'; + fputs(str.c_str(), stderr); } void PrintColoredMessage(const Entry& entry) { #ifdef _WIN32 - static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); + HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); + if (console_handle == INVALID_HANDLE_VALUE) { + return; + } CONSOLE_SCREEN_BUFFER_INFO original_info = {0}; GetConsoleScreenBufferInfo(console_handle, &original_info); diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 66e86d9ec..c587faefb 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -10,20 +10,8 @@ namespace Log { struct Entry; -/** - * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's - * intended to be used to strip a system-specific build directory from the `__FILE__` macro, - * leaving only the path relative to the sources root. - * - * @param path The input file path as a null-terminated string - * @param root The name of the root source directory as a null-terminated string. Path up to and - * including the last occurrence of this name will be stripped - * @return A pointer to the same string passed as `path`, but starting at the trimmed portion - */ -const char* TrimSourcePath(const char* path, const char* root = "src"); - /// Formats a log entry into the provided text buffer. -void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); +std::string FormatLogMessage(const Entry& entry); /// Formats and prints a log entry to stderr. void PrintMessage(const Entry& entry); /// Prints the same message as `PrintMessage`, but colored acoording to the severity level. diff --git a/src/common/math_util.h b/src/common/math_util.h index 45a1ed367..c6a83c953 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -17,11 +17,6 @@ inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); } -template <typename T> -inline T Clamp(const T val, const T& min, const T& max) { - return std::max(min, std::min(max, val)); -} - template <class T> struct Rectangle { T left; diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index 759ad02ca..4d1ec8fb9 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -55,7 +55,7 @@ void* AllocateExecutableMemory(size_t size, bool low) { if (ptr == MAP_FAILED) { ptr = nullptr; #endif - LOG_ERROR(Common_Memory, "Failed to allocate executable memory"); + NGLOG_ERROR(Common_Memory, "Failed to allocate executable memory"); } #if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT) else { @@ -68,7 +68,7 @@ void* AllocateExecutableMemory(size_t size, bool low) { #if EMU_ARCH_BITS == 64 if ((u64)ptr >= 0x80000000 && low == true) - LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!"); + NGLOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!"); #endif return ptr; @@ -85,7 +85,7 @@ void* AllocateMemoryPages(size_t size) { #endif if (ptr == nullptr) - LOG_ERROR(Common_Memory, "Failed to allocate raw memory"); + NGLOG_ERROR(Common_Memory, "Failed to allocate raw memory"); return ptr; } @@ -99,12 +99,12 @@ void* AllocateAlignedMemory(size_t size, size_t alignment) { ptr = memalign(alignment, size); #else if (posix_memalign(&ptr, alignment, size) != 0) - LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); + NGLOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); #endif #endif if (ptr == nullptr) - LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); + NGLOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); return ptr; } @@ -113,7 +113,7 @@ void FreeMemoryPages(void* ptr, size_t size) { if (ptr) { #ifdef _WIN32 if (!VirtualFree(ptr, 0, MEM_RELEASE)) - LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n%s", GetLastErrorMsg()); + NGLOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg()); #else munmap(ptr, size); #endif @@ -134,7 +134,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) - LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n%s", GetLastErrorMsg()); + NGLOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg()); #else mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ); #endif @@ -145,7 +145,7 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) { DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &oldValue)) - LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n%s", GetLastErrorMsg()); + NGLOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg()); #else mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ); @@ -167,8 +167,7 @@ std::string MemUsage() { return "MemUsage Error"; if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) - Ret = Common::StringFromFormat( - "%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str()); + Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7)); CloseHandle(hProcess); return Ret; diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp index 3a6ef8c27..ab0154133 100644 --- a/src/common/param_package.cpp +++ b/src/common/param_package.cpp @@ -25,7 +25,7 @@ ParamPackage::ParamPackage(const std::string& serialized) { std::vector<std::string> key_value; Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value); if (key_value.size() != 2) { - LOG_ERROR(Common, "invalid key pair %s", pair.c_str()); + NGLOG_ERROR(Common, "invalid key pair {}", pair); continue; } @@ -64,7 +64,7 @@ std::string ParamPackage::Serialize() const { std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const { auto pair = data.find(key); if (pair == data.end()) { - LOG_DEBUG(Common, "key %s not found", key.c_str()); + NGLOG_DEBUG(Common, "key '{}' not found", key); return default_value; } @@ -74,14 +74,14 @@ std::string ParamPackage::Get(const std::string& key, const std::string& default int ParamPackage::Get(const std::string& key, int default_value) const { auto pair = data.find(key); if (pair == data.end()) { - LOG_DEBUG(Common, "key %s not found", key.c_str()); + NGLOG_DEBUG(Common, "key '{}' not found", key); return default_value; } try { return std::stoi(pair->second); } catch (const std::logic_error&) { - LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str()); + NGLOG_ERROR(Common, "failed to convert {} to int", pair->second); return default_value; } } @@ -89,14 +89,14 @@ int ParamPackage::Get(const std::string& key, int default_value) const { float ParamPackage::Get(const std::string& key, float default_value) const { auto pair = data.find(key); if (pair == data.end()) { - LOG_DEBUG(Common, "key %s not found", key.c_str()); + NGLOG_DEBUG(Common, "key {} not found", key); return default_value; } try { return std::stof(pair->second); } catch (const std::logic_error&) { - LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str()); + NGLOG_ERROR(Common, "failed to convert {} to float", pair->second); return default_value; } } diff --git a/src/common/platform.h b/src/common/platform.h deleted file mode 100644 index c62fb7c8f..000000000 --- a/src/common/platform.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (C) 2005-2012 Gekko Emulator - * - * @file platform.h - * @author ShizZy <shizzy247@gmail.com> - * @date 2012-02-11 - * @brief Platform detection macros for portable compilation - * - * @section LICENSE - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details at - * http://www.gnu.org/copyleft/gpl.html - * - * Official project repository can be found at: - * http://code.google.com/p/gekko-gc-emu/ - */ - -#pragma once - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Platform detection - -#if defined(ARCHITECTURE_x86_64) || defined(__aarch64__) -#define EMU_ARCH_BITS 64 -#elif defined(__i386) || defined(_M_IX86) || defined(__arm__) || defined(_M_ARM) -#define EMU_ARCH_BITS 32 -#endif diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index e9a2a6b00..646400db0 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -46,76 +46,6 @@ bool AsciiToHex(const char* _szValue, u32& result) { return true; } -bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) { - int writtenCount; - -#ifdef _MSC_VER - // You would think *printf are simple, right? Iterate on each character, - // if it's a format specifier handle it properly, etc. - // - // Nooooo. Not according to the C standard. - // - // According to the C99 standard (7.19.6.1 "The fprintf function") - // The format shall be a multibyte character sequence - // - // Because some character encodings might have '%' signs in the middle of - // a multibyte sequence (SJIS for example only specifies that the first - // byte of a 2 byte sequence is "high", the second byte can be anything), - // printf functions have to decode the multibyte sequences and try their - // best to not screw up. - // - // Unfortunately, on Windows, the locale for most languages is not UTF-8 - // as we would need. Notably, for zh_TW, Windows chooses EUC-CN as the - // locale, and completely fails when trying to decode UTF-8 as EUC-CN. - // - // On the other hand, the fix is simple: because we use UTF-8, no such - // multibyte handling is required as we can simply assume that no '%' char - // will be present in the middle of a multibyte sequence. - // - // This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l. - static locale_t c_locale = nullptr; - if (!c_locale) - c_locale = _create_locale(LC_ALL, ".1252"); - writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args); -#else - writtenCount = vsnprintf(out, outsize, format, args); -#endif - - if (writtenCount > 0 && writtenCount < outsize) { - out[writtenCount] = '\0'; - return true; - } else { - out[outsize - 1] = '\0'; - return false; - } -} - -std::string StringFromFormat(const char* format, ...) { - va_list args; - char* buf = nullptr; -#ifdef _WIN32 - int required = 0; - - va_start(args, format); - required = _vscprintf(format, args); - buf = new char[required + 1]; - CharArrayFromFormatV(buf, required + 1, format, args); - va_end(args); - - std::string temp = buf; - delete[] buf; -#else - va_start(args, format); - if (vasprintf(&buf, format, args) < 0) - LOG_ERROR(Common, "Unable to allocate memory for string"); - va_end(args); - - std::string temp = buf; - free(buf); -#endif - return temp; -} - // For Debugging. Read out an u8 array. std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) { std::ostringstream oss; @@ -134,6 +64,10 @@ std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces return oss.str(); } +std::string StringFromBuffer(const std::vector<u8>& data) { + return std::string(data.begin(), std::find(data.begin(), data.end(), '\0')); +} + // Turns " hej " into "hej". Also handles tabs. std::string StripSpaces(const std::string& str) { const size_t s = str.find_first_not_of(" \t\r\n"); @@ -347,7 +281,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& iconv_t const conv_desc = iconv_open("UTF-8", fromcode); if ((iconv_t)(-1) == conv_desc) { - LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno)); + NGLOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno)); iconv_close(conv_desc); return {}; } @@ -376,7 +310,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& ++src_buffer; } } else { - LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno)); + NGLOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno)); break; } } @@ -395,7 +329,7 @@ std::u16string UTF8ToUTF16(const std::string& input) { iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8"); if ((iconv_t)(-1) == conv_desc) { - LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno)); + NGLOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno)); iconv_close(conv_desc); return {}; } @@ -424,7 +358,7 @@ std::u16string UTF8ToUTF16(const std::string& input) { ++src_buffer; } } else { - LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno)); + NGLOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno)); break; } } @@ -462,4 +396,27 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l return std::string(buffer, len); } + +const char* TrimSourcePath(const char* path, const char* root) { + const char* p = path; + + while (*p != '\0') { + const char* next_slash = p; + while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { + ++next_slash; + } + + bool is_src = Common::ComparePartialString(p, next_slash, root); + p = next_slash; + + if (*p != '\0') { + ++p; + } + if (is_src) { + path = p; + } + } + return path; +} + } // namespace Common diff --git a/src/common/string_util.h b/src/common/string_util.h index ceb8df48e..1f5a383cb 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -4,7 +4,6 @@ #pragma once -#include <cstdarg> #include <cstddef> #include <iomanip> #include <sstream> @@ -20,21 +19,10 @@ std::string ToLower(std::string str); /// Make a string uppercase std::string ToUpper(std::string str); -std::string StringFromFormat(const char* format, ...); -// Cheap! -bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args); - -template <size_t Count> -inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) { - va_list args; - va_start(args, format); - CharArrayFromFormatV(out, Count, format, args); - va_end(args); -} - -// Good std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true); +std::string StringFromBuffer(const std::vector<u8>& data); + std::string StripSpaces(const std::string& s); std::string StripQuotes(const std::string& s); @@ -134,4 +122,17 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { * NUL-terminated then the string ends at max_len characters. */ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); + +/** + * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's + * intended to be used to strip a system-specific build directory from the `__FILE__` macro, + * leaving only the path relative to the sources root. + * + * @param path The input file path as a null-terminated string + * @param root The name of the root source directory as a null-terminated string. Path up to and + * including the last occurrence of this name will be stripped + * @return A pointer to the same string passed as `path`, but starting at the trimmed portion + */ +const char* TrimSourcePath(const char* path, const char* root = "src"); + } // namespace Common diff --git a/src/common/swap.h b/src/common/swap.h index d94cbe6b2..4a4012d1a 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -103,7 +103,19 @@ inline __attribute__((always_inline)) u64 swap64(u64 _data) { return __builtin_bswap64(_data); } #elif defined(__Bitrig__) || defined(__OpenBSD__) -// swap16, swap32, swap64 are left as is +// redefine swap16, swap32, swap64 as inline functions +#undef swap16 +#undef swap32 +#undef swap64 +inline u16 swap16(u16 _data) { + return __swap16(_data); +} +inline u32 swap32(u32 _data) { + return __swap32(_data); +} +inline u64 swap64(u64 _data) { + return __swap64(_data); +} #elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) inline u16 swap16(u16 _data) { return bswap16(_data); diff --git a/src/common/telemetry.h b/src/common/telemetry.h index 3694c76f2..7a09df0a7 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -15,7 +15,7 @@ namespace Telemetry { /// Field type, used for grouping fields together in the final submitted telemetry log enum class FieldType : u8 { None = 0, ///< No specified field group - App, ///< Citra application fields (e.g. version, branch, etc.) + App, ///< yuzu application fields (e.g. version, branch, etc.) Session, ///< Emulated session fields (e.g. title ID, log, etc.) Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.) UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.) diff --git a/src/common/thread.h b/src/common/thread.h index fa475ab51..9465e1de7 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -11,25 +11,6 @@ #include <thread> #include "common/common_types.h" -// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very -// recently. Fortunately, thread local variables have been well supported for compilers for a while, -// but with semantics supporting only POD types, so we can use a few defines to get some amount of -// backwards compat support. -// WARNING: This only works correctly with POD types. -#if defined(__clang__) -#if !__has_feature(cxx_thread_local) -#define thread_local __thread -#endif -#elif defined(__GNUC__) -#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) -#define thread_local __thread -#endif -#elif defined(_MSC_VER) -#if _MSC_VER < 1900 -#define thread_local __declspec(thread) -#endif -#endif - namespace Common { int CurrentThreadId(); diff --git a/src/common/timer.cpp b/src/common/timer.cpp index c9803109e..f0c5b1a43 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -2,7 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <time.h> +#include <ctime> + +#include <fmt/format.h> + #ifdef _WIN32 #include <windows.h> // windows.h needs to be included before other windows headers @@ -104,8 +107,8 @@ std::string Timer::GetTimeElapsedFormatted() const { // Hours u32 Hours = Minutes / 60; - std::string TmpStr = StringFromFormat("%02i:%02i:%02i:%03i", Hours, Minutes % 60, Seconds % 60, - Milliseconds % 1000); + std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours, Minutes % 60, Seconds % 60, + Milliseconds % 1000); return TmpStr; } @@ -165,11 +168,11 @@ std::string Timer::GetTimeFormatted() { #ifdef _WIN32 struct timeb tp; (void)::ftime(&tp); - return StringFromFormat("%s:%03i", tmp, tp.millitm); + return fmt::format("{}:{:03}", tmp, tp.millitm); #else struct timeval t; (void)gettimeofday(&t, nullptr); - return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000)); + return fmt::format("{}:{:03}", tmp, static_cast<int>(t.tv_usec / 1000)); #endif } diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 3f0057d9e..cca43bd4c 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -52,12 +52,8 @@ static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w); template <typename T> class Vec2 { public: - T x; - T y; - - T* AsArray() { - return &x; - } + T x{}; + T y{}; Vec2() = default; Vec2(const T& _x, const T& _y) : x(_x), y(_y) {} @@ -71,11 +67,6 @@ public: return Vec2<T>(f, f); } - void Write(T a[2]) { - a[0] = x; - a[1] = y; - } - Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { return MakeVec(x + other.x, y + other.y); } @@ -201,13 +192,9 @@ inline float Vec2<float>::Normalize() { template <typename T> class Vec3 { public: - T x; - T y; - T z; - - T* AsArray() { - return &x; - } + T x{}; + T y{}; + T z{}; Vec3() = default; Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {} @@ -225,12 +212,6 @@ public: return MakeVec(f, f, f); } - void Write(T a[3]) { - a[0] = x; - a[1] = y; - a[2] = z; - } - Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const { return MakeVec(x + other.x, y + other.y, z + other.z); } @@ -411,14 +392,10 @@ typedef Vec3<float> Vec3f; template <typename T> class Vec4 { public: - T x; - T y; - T z; - T w; - - T* AsArray() { - return &x; - } + T x{}; + T y{}; + T z{}; + T w{}; Vec4() = default; Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {} @@ -436,13 +413,6 @@ public: return Vec4<T>(f, f, f, f); } - void Write(T a[4]) { - a[0] = x; - a[1] = y; - a[2] = z; - a[3] = w; - } - Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const { return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w); } diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 62f17fbb5..2dfcd39c8 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -54,7 +54,7 @@ static CPUCaps Detect() { caps.num_cores = std::thread::hardware_concurrency(); // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support - // Citra at all anyway + // yuzu at all anyway int cpu_id[4]; memset(caps.brand_string, 0, sizeof(caps.brand_string)); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1bc536075..ba5b02174 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -4,12 +4,18 @@ add_library(core STATIC arm/unicorn/arm_unicorn.h core.cpp core.h + core_cpu.cpp + core_cpu.h core_timing.cpp core_timing.h file_sys/directory.h + file_sys/disk_filesystem.cpp + file_sys/disk_filesystem.h file_sys/errors.h file_sys/filesystem.cpp file_sys/filesystem.h + file_sys/partition_filesystem.cpp + file_sys/partition_filesystem.h file_sys/path_parser.cpp file_sys/path_parser.h file_sys/program_metadata.cpp @@ -18,6 +24,10 @@ add_library(core STATIC file_sys/romfs_factory.h file_sys/romfs_filesystem.cpp file_sys/romfs_filesystem.h + file_sys/savedata_factory.cpp + file_sys/savedata_factory.h + file_sys/sdmc_factory.cpp + file_sys/sdmc_factory.h file_sys/storage.h frontend/emu_window.cpp frontend/emu_window.h @@ -34,8 +44,6 @@ add_library(core STATIC hle/kernel/client_port.h hle/kernel/client_session.cpp hle/kernel/client_session.h - hle/kernel/condition_variable.cpp - hle/kernel/condition_variable.h hle/kernel/errors.h hle/kernel/event.cpp hle/kernel/event.h @@ -82,8 +90,14 @@ add_library(core STATIC hle/romfs.h hle/service/acc/acc.cpp hle/service/acc/acc.h + hle/service/acc/acc_aa.cpp + hle/service/acc/acc_aa.h + hle/service/acc/acc_su.cpp + hle/service/acc/acc_su.h hle/service/acc/acc_u0.cpp hle/service/acc/acc_u0.h + hle/service/acc/acc_u1.cpp + hle/service/acc/acc_u1.h hle/service/am/am.cpp hle/service/am/am.h hle/service/am/applet_ae.cpp @@ -110,6 +124,16 @@ add_library(core STATIC hle/service/audio/audren_u.h hle/service/audio/codecctl.cpp hle/service/audio/codecctl.h + hle/service/bcat/module.cpp + hle/service/bcat/module.h + hle/service/bcat/bcat.cpp + hle/service/bcat/bcat.h + hle/service/fatal/fatal.cpp + hle/service/fatal/fatal.h + hle/service/fatal/fatal_p.cpp + hle/service/fatal/fatal_p.h + hle/service/fatal/fatal_u.cpp + hle/service/fatal/fatal_u.h hle/service/filesystem/filesystem.cpp hle/service/filesystem/filesystem.h hle/service/filesystem/fsp_srv.cpp @@ -118,10 +142,14 @@ add_library(core STATIC hle/service/friend/friend.h hle/service/friend/friend_a.cpp hle/service/friend/friend_a.h + hle/service/friend/friend_u.cpp + hle/service/friend/friend_u.h hle/service/hid/hid.cpp hle/service/hid/hid.h hle/service/lm/lm.cpp hle/service/lm/lm.h + hle/service/mm/mm_u.cpp + hle/service/mm/mm_u.h hle/service/nifm/nifm.cpp hle/service/nifm/nifm.h hle/service/nifm/nifm_a.cpp @@ -130,6 +158,10 @@ add_library(core STATIC hle/service/nifm/nifm_s.h hle/service/nifm/nifm_u.cpp hle/service/nifm/nifm_u.h + hle/service/nfp/nfp.cpp + hle/service/nfp/nfp.h + hle/service/nfp/nfp_user.cpp + hle/service/nfp/nfp_user.h hle/service/ns/ns.cpp hle/service/ns/ns.h hle/service/ns/pl_u.cpp @@ -145,6 +177,8 @@ add_library(core STATIC hle/service/nvdrv/devices/nvhost_ctrl_gpu.h hle/service/nvdrv/devices/nvhost_gpu.cpp hle/service/nvdrv/devices/nvhost_gpu.h + hle/service/nvdrv/devices/nvhost_nvdec.cpp + hle/service/nvdrv/devices/nvhost_nvdec.h hle/service/nvdrv/devices/nvmap.cpp hle/service/nvdrv/devices/nvmap.h hle/service/nvdrv/interface.cpp @@ -157,24 +191,44 @@ add_library(core STATIC hle/service/nvflinger/buffer_queue.h hle/service/nvflinger/nvflinger.cpp hle/service/nvflinger/nvflinger.h + hle/service/pctl/module.cpp + hle/service/pctl/module.h hle/service/pctl/pctl.cpp hle/service/pctl/pctl.h - hle/service/pctl/pctl_a.cpp - hle/service/pctl/pctl_a.h + hle/service/prepo/prepo.cpp + hle/service/prepo/prepo.h hle/service/service.cpp hle/service/service.h hle/service/set/set.cpp hle/service/set/set.h + hle/service/set/set_cal.cpp + hle/service/set/set_cal.h + hle/service/set/set_fd.cpp + hle/service/set/set_fd.h + hle/service/set/set_sys.cpp + hle/service/set/set_sys.h + hle/service/set/settings.cpp + hle/service/set/settings.h hle/service/sm/controller.cpp hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h - hle/service/sockets/bsd_u.cpp - hle/service/sockets/bsd_u.h + hle/service/sockets/bsd.cpp + hle/service/sockets/bsd.h + hle/service/sockets/nsd.cpp + hle/service/sockets/nsd.h hle/service/sockets/sfdnsres.cpp hle/service/sockets/sfdnsres.h hle/service/sockets/sockets.cpp hle/service/sockets/sockets.h + hle/service/spl/csrng.cpp + hle/service/spl/csrng.h + hle/service/spl/module.cpp + hle/service/spl/module.h + hle/service/spl/spl.cpp + hle/service/spl/spl.h + hle/service/ssl/ssl.cpp + hle/service/ssl/ssl.h hle/service/time/time.cpp hle/service/time/time.h hle/service/time/time_s.cpp @@ -209,6 +263,7 @@ add_library(core STATIC loader/nso.h memory.cpp memory.h + memory_hook.cpp memory_hook.h memory_setup.h perf_stats.cpp diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 5ae60214e..32ff3c345 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -25,22 +25,18 @@ public: VAddr tls_address; }; - /** - * Runs the CPU for the given number of instructions - * @param num_instructions Number of instructions to run - */ - void Run(int num_instructions) { - ExecuteInstructions(num_instructions); - this->num_instructions += num_instructions; - } + /// Runs the CPU until an event happens + virtual void Run() = 0; /// Step CPU by one instruction - void Step() { - Run(1); - } + virtual void Step() = 0; + /// Maps a backing memory region for the CPU virtual void MapBackingMemory(VAddr address, size_t size, u8* memory, - Kernel::VMAPermission perms) {} + Kernel::VMAPermission perms) = 0; + + /// Unmaps a region of memory that was previously mapped using MapBackingMemory + virtual void UnmapMemory(VAddr address, size_t size) = 0; /// Clear all instruction cache virtual void ClearInstructionCache() = 0; @@ -122,19 +118,4 @@ public: /// Prepare core for thread reschedule (if needed to correctly handle state) virtual void PrepareReschedule() = 0; - - /// Getter for num_instructions - u64 GetNumInstructions() const { - return num_instructions; - } - -protected: - /** - * Executes the given number of instructions - * @param num_instructions Number of instructions to executes - */ - virtual void ExecuteInstructions(int num_instructions) = 0; - -private: - u64 num_instructions = 0; ///< Number of instructions executed }; diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index e7f6bf8c2..b5db47667 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -8,6 +8,7 @@ #include <dynarmic/A64/config.h> #include "common/logging/log.h" #include "core/arm/dynarmic/arm_dynarmic.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/svc.h" @@ -54,8 +55,8 @@ public: } void InterpreterFallback(u64 pc, size_t num_instructions) override { - LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)", - pc, num_instructions, MemoryReadCode(pc)); + NGLOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc, + num_instructions, MemoryReadCode(pc)); ARM_Interface::ThreadContext ctx; parent.SaveContext(ctx); @@ -75,7 +76,7 @@ public: case Dynarmic::A64::Exception::Yield: return; default: - ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")", + ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})", static_cast<size_t>(exception), pc); } } @@ -85,28 +86,24 @@ public: } void AddTicks(u64 ticks) override { - if (ticks > ticks_remaining) { - ticks_remaining = 0; - return; - } - ticks -= ticks_remaining; + CoreTiming::AddTicks(ticks - num_interpreted_instructions); + num_interpreted_instructions = 0; } u64 GetTicksRemaining() override { - return ticks_remaining; + return std::max(CoreTiming::GetDowncount(), 0); } u64 GetCNTPCT() override { return CoreTiming::GetTicks(); } ARM_Dynarmic& parent; - size_t ticks_remaining = 0; size_t num_interpreted_instructions = 0; u64 tpidrro_el0 = 0; u64 tpidr_el0 = 0; }; std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_Callbacks>& cb) { - const auto page_table = Kernel::g_current_process->vm_manager.page_table.pointers.data(); + const auto page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data(); Dynarmic::A64::UserConfig config; config.callbacks = cb.get(); @@ -121,11 +118,22 @@ std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_C return std::make_unique<Dynarmic::A64::Jit>(config); } +void ARM_Dynarmic::Run() { + ASSERT(Memory::GetCurrentPageTable() == current_page_table); + + jit->Run(); +} + +void ARM_Dynarmic::Step() { + cb->InterpreterFallback(jit->GetPC(), 1); +} + ARM_Dynarmic::ARM_Dynarmic() : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), jit(MakeJit(cb)) { ARM_Interface::ThreadContext ctx; inner_unicorn.SaveContext(ctx); LoadContext(ctx); + PageTableChanged(); } ARM_Dynarmic::~ARM_Dynarmic() = default; @@ -135,6 +143,10 @@ void ARM_Dynarmic::MapBackingMemory(u64 address, size_t size, u8* memory, inner_unicorn.MapBackingMemory(address, size, memory, perms); } +void ARM_Dynarmic::UnmapMemory(u64 address, size_t size) { + inner_unicorn.UnmapMemory(address, size); +} + void ARM_Dynarmic::SetPC(u64 pc) { jit->SetPC(pc); } @@ -184,13 +196,6 @@ void ARM_Dynarmic::SetTlsAddress(u64 address) { cb->tpidrro_el0 = address; } -void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { - cb->ticks_remaining = num_instructions; - jit->Run(); - CoreTiming::AddTicks(num_instructions - cb->num_interpreted_instructions); - cb->num_interpreted_instructions = 0; -} - void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) { ctx.cpu_registers = jit->GetRegisters(); ctx.sp = jit->GetSP(); @@ -223,4 +228,5 @@ void ARM_Dynarmic::ClearInstructionCache() { void ARM_Dynarmic::PageTableChanged() { jit = MakeJit(cb); + current_page_table = Memory::GetCurrentPageTable(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 1d9dcf5ff..128669d01 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -19,7 +19,7 @@ public: void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override; - + void UnmapMemory(u64 address, size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; @@ -29,6 +29,8 @@ public: u32 GetVFPReg(int index) const override; void SetVFPReg(int index, u32 value) override; u32 GetCPSR() const override; + void Run() override; + void Step() override; void SetCPSR(u32 cpsr) override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; @@ -37,7 +39,6 @@ public: void LoadContext(const ThreadContext& ctx) override; void PrepareReschedule() override; - void ExecuteInstructions(int num_instructions) override; void ClearInstructionCache() override; void PageTableChanged() override; @@ -47,4 +48,6 @@ private: std::unique_ptr<ARM_Dynarmic_Callbacks> cb; std::unique_ptr<Dynarmic::A64::Jit> jit; ARM_Unicorn inner_unicorn; + + Memory::PageTable* current_page_table = nullptr; }; diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp index 5d2956bfd..ce6c5616d 100644 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ b/src/core/arm/unicorn/arm_unicorn.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <unicorn/arm64.h> #include "common/assert.h" #include "common/microprofile.h" @@ -29,11 +30,22 @@ LoadDll LoadDll::g_load_dll; #define CHECKED(expr) \ do { \ if (auto _cerr = (expr)) { \ - ASSERT_MSG(false, "Call " #expr " failed with error: %u (%s)\n", _cerr, \ + ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \ uc_strerror(_cerr)); \ } \ } while (0) +static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) { + GDBStub::BreakpointAddress bkpt = + GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute); + if (GDBStub::IsMemoryBreak() || + (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) { + auto core = static_cast<ARM_Unicorn*>(user_data); + core->RecordBreak(bkpt); + uc_emu_stop(uc); + } +} + static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) { u32 esr{}; CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr)); @@ -51,8 +63,8 @@ static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) { static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data) { ARM_Interface::ThreadContext ctx{}; - Core::CPU().SaveContext(ctx); - ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%llx, pc=0x%llx, lr=0x%llx", addr, + Core::CurrentArmInterface().SaveContext(ctx); + ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr, ctx.pc, ctx.cpu_registers[30]); return {}; } @@ -66,6 +78,10 @@ ARM_Unicorn::ARM_Unicorn() { uc_hook hook{}; CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1)); CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1)); + if (GDBStub::IsServerEnabled()) { + CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1)); + last_bkpt_hit = false; + } } ARM_Unicorn::~ARM_Unicorn() { @@ -77,6 +93,10 @@ void ARM_Unicorn::MapBackingMemory(VAddr address, size_t size, u8* memory, CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory)); } +void ARM_Unicorn::UnmapMemory(VAddr address, size_t size) { + CHECKED(uc_mem_unmap(uc, address, size)); +} + void ARM_Unicorn::SetPC(u64 pc) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); } @@ -149,12 +169,36 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); } +void ARM_Unicorn::Run() { + if (GDBStub::IsServerEnabled()) { + ExecuteInstructions(std::max(4000000, 0)); + } else { + ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0)); + } +} + +void ARM_Unicorn::Step() { + ExecuteInstructions(1); +} + MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); void ARM_Unicorn::ExecuteInstructions(int num_instructions) { MICROPROFILE_SCOPE(ARM_Jit); CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); CoreTiming::AddTicks(num_instructions); + if (GDBStub::IsServerEnabled()) { + if (last_bkpt_hit) { + uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); + } + Kernel::Thread* thread = Kernel::GetCurrentThread(); + SaveContext(thread->context); + if (last_bkpt_hit) { + last_bkpt_hit = false; + GDBStub::Break(); + } + GDBStub::SendTrap(thread, 5); + } } void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) { @@ -220,3 +264,8 @@ void ARM_Unicorn::PrepareReschedule() { } void ARM_Unicorn::ClearInstructionCache() {} + +void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) { + last_bkpt = bkpt; + last_bkpt_hit = true; +} diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h index c9a561dec..a482a2aa3 100644 --- a/src/core/arm/unicorn/arm_unicorn.h +++ b/src/core/arm/unicorn/arm_unicorn.h @@ -7,6 +7,7 @@ #include <unicorn/unicorn.h> #include "common/common_types.h" #include "core/arm/arm_interface.h" +#include "core/gdbstub/gdbstub.h" class ARM_Unicorn final : public ARM_Interface { public: @@ -14,6 +15,7 @@ public: ~ARM_Unicorn(); void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override; + void UnmapMemory(VAddr address, size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; @@ -29,10 +31,15 @@ public: void SaveContext(ThreadContext& ctx) override; void LoadContext(const ThreadContext& ctx) override; void PrepareReschedule() override; - void ExecuteInstructions(int num_instructions) override; + void ExecuteInstructions(int num_instructions); + void Run() override; + void Step() override; void ClearInstructionCache() override; void PageTableChanged() override{}; + void RecordBreak(GDBStub::BreakpointAddress bkpt); private: uc_engine* uc{}; + GDBStub::BreakpointAddress last_bkpt{}; + bool last_bkpt_hit; }; diff --git a/src/core/core.cpp b/src/core/core.cpp index 4fb035556..84ab876cc 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -5,17 +5,16 @@ #include <memory> #include <utility> #include "common/logging/log.h" -#ifdef ARCHITECTURE_x86_64 -#include "core/arm/dynarmic/arm_dynarmic.h" -#endif -#include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/service.h" +#include "core/hle/service/sm/controller.h" +#include "core/hle/service/sm/sm.h" #include "core/hw/hw.h" #include "core/loader/loader.h" #include "core/memory_setup.h" @@ -26,12 +25,34 @@ namespace Core { /*static*/ System System::s_instance; -System::ResultStatus System::RunLoop(int tight_loop) { - status = ResultStatus::Success; - if (!cpu_core) { - return ResultStatus::ErrorNotInitialized; +System::~System() = default; + +/// Runs a CPU core while the system is powered on +static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { + while (Core::System().GetInstance().IsPoweredOn()) { + cpu_state->RunLoop(true); + } +} + +Cpu& System::CurrentCpuCore() { + // If multicore is enabled, use host thread to figure out the current CPU core + if (Settings::values.use_multi_core) { + const auto& search = thread_to_cpu.find(std::this_thread::get_id()); + ASSERT(search != thread_to_cpu.end()); + ASSERT(search->second); + return *search->second; } + // Otherwise, use single-threaded mode active_core variable + return *cpu_cores[active_core]; +} + +System::ResultStatus System::RunLoop(bool tight_loop) { + status = ResultStatus::Success; + + // Update thread_to_cpu in case Core 0 is run from a different host thread + thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0]; + if (GDBStub::IsServerEnabled()) { GDBStub::HandlePacket(); @@ -40,54 +61,49 @@ System::ResultStatus System::RunLoop(int tight_loop) { if (GDBStub::GetCpuHaltFlag()) { if (GDBStub::GetCpuStepFlag()) { GDBStub::SetCpuStepFlag(false); - tight_loop = 1; + tight_loop = false; } else { return ResultStatus::Success; } } } - // If we don't have a currently active thread then don't execute instructions, - // instead advance to the next event and try to yield to the next thread - if (Kernel::GetCurrentThread() == nullptr) { - LOG_TRACE(Core_ARM, "Idling"); - CoreTiming::Idle(); - CoreTiming::Advance(); - PrepareReschedule(); - } else { - CoreTiming::Advance(); - cpu_core->Run(tight_loop); + for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) { + cpu_cores[active_core]->RunLoop(tight_loop); + if (Settings::values.use_multi_core) { + // Cores 1-3 are run on other threads in this mode + break; + } } - HW::Update(); - Reschedule(); - return status; } System::ResultStatus System::SingleStep() { - return RunLoop(1); + return RunLoop(false); } System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { app_loader = Loader::GetLoader(filepath); if (!app_loader) { - LOG_CRITICAL(Core, "Failed to obtain loader for %s!", filepath.c_str()); + NGLOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); return ResultStatus::ErrorGetLoader; } std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode = app_loader->LoadKernelSystemMode(); if (system_mode.second != Loader::ResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!", - static_cast<int>(system_mode.second)); + NGLOG_CRITICAL(Core, "Failed to determine system mode (Error {})!", + static_cast<int>(system_mode.second)); switch (system_mode.second) { case Loader::ResultStatus::ErrorEncrypted: return ResultStatus::ErrorLoader_ErrorEncrypted; case Loader::ResultStatus::ErrorInvalidFormat: return ResultStatus::ErrorLoader_ErrorInvalidFormat; + case Loader::ResultStatus::ErrorUnsupportedArch: + return ResultStatus::ErrorUnsupportedArch; default: return ResultStatus::ErrorSystemMode; } @@ -95,14 +111,15 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file ResultStatus init_result{Init(emu_window, system_mode.first.get())}; if (init_result != ResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", init_result); + NGLOG_CRITICAL(Core, "Failed to initialize system (Error {})!", + static_cast<int>(init_result)); System::Shutdown(); return init_result; } - const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)}; + const Loader::ResultStatus load_result{app_loader->Load(current_process)}; if (Loader::ResultStatus::Success != load_result) { - LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result); + NGLOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); System::Shutdown(); switch (load_result) { @@ -110,6 +127,8 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file return ResultStatus::ErrorLoader_ErrorEncrypted; case Loader::ResultStatus::ErrorInvalidFormat: return ResultStatus::ErrorLoader_ErrorInvalidFormat; + case Loader::ResultStatus::ErrorUnsupportedArch: + return ResultStatus::ErrorUnsupportedArch; default: return ResultStatus::ErrorLoader; } @@ -119,58 +138,65 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file } void System::PrepareReschedule() { - cpu_core->PrepareReschedule(); - reschedule_pending = true; + CurrentCpuCore().PrepareReschedule(); } PerfStats::Results System::GetAndResetPerfStats() { return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); } -void System::Reschedule() { - if (!reschedule_pending) { - return; - } +const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) { + ASSERT(core_index < NUM_CPU_CORES); + return cpu_cores[core_index]->Scheduler(); +} - reschedule_pending = false; - Core::System::GetInstance().Scheduler().Reschedule(); +ARM_Interface& System::ArmInterface(size_t core_index) { + ASSERT(core_index < NUM_CPU_CORES); + return cpu_cores[core_index]->ArmInterface(); +} + +Cpu& System::CpuCore(size_t core_index) { + ASSERT(core_index < NUM_CPU_CORES); + return *cpu_cores[core_index]; } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { - LOG_DEBUG(HW_Memory, "initialized OK"); + NGLOG_DEBUG(HW_Memory, "initialized OK"); CoreTiming::Init(); - switch (Settings::values.cpu_core) { - case Settings::CpuCore::Unicorn: - cpu_core = std::make_shared<ARM_Unicorn>(); - break; - case Settings::CpuCore::Dynarmic: - default: -#ifdef ARCHITECTURE_x86_64 - cpu_core = std::make_shared<ARM_Dynarmic>(); -#else - cpu_core = std::make_shared<ARM_Unicorn>(); - LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); -#endif - break; + current_process = Kernel::Process::Create("main"); + + cpu_barrier = std::make_shared<CpuBarrier>(); + for (size_t index = 0; index < cpu_cores.size(); ++index) { + cpu_cores[index] = std::make_shared<Cpu>(cpu_barrier, index); } gpu_core = std::make_unique<Tegra::GPU>(); - telemetry_session = std::make_unique<Core::TelemetrySession>(); + service_manager = std::make_shared<Service::SM::ServiceManager>(); HW::Init(); Kernel::Init(system_mode); - scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get()); - Service::Init(); + Service::Init(service_manager); GDBStub::Init(); if (!VideoCore::Init(emu_window)) { return ResultStatus::ErrorVideoCore; } - LOG_DEBUG(Core, "Initialized OK"); + // Create threads for CPU cores 1-3, and build thread_to_cpu map + // CPU core 0 is run on the main thread + thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0]; + if (Settings::values.use_multi_core) { + for (size_t index = 0; index < cpu_core_threads.size(); ++index) { + cpu_core_threads[index] = + std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]); + thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1]; + } + } + + NGLOG_DEBUG(Core, "Initialized OK"); // Reset counters and set time origin to current frame GetAndResetPerfStats(); @@ -193,17 +219,41 @@ void System::Shutdown() { VideoCore::Shutdown(); GDBStub::Shutdown(); Service::Shutdown(); - scheduler = nullptr; Kernel::Shutdown(); HW::Shutdown(); - telemetry_session = nullptr; - gpu_core = nullptr; - cpu_core = nullptr; + service_manager.reset(); + telemetry_session.reset(); + gpu_core.reset(); + + // Close all CPU/threading state + cpu_barrier->NotifyEnd(); + if (Settings::values.use_multi_core) { + for (auto& thread : cpu_core_threads) { + thread->join(); + thread.reset(); + } + } + thread_to_cpu.clear(); + for (auto& cpu_core : cpu_cores) { + cpu_core.reset(); + } + cpu_barrier.reset(); + + // Close core timing CoreTiming::Shutdown(); - app_loader = nullptr; + // Close app loader + app_loader.reset(); + + NGLOG_DEBUG(Core, "Shutdown OK"); +} + +Service::SM::ServiceManager& System::ServiceManager() { + return *service_manager; +} - LOG_DEBUG(Core, "Shutdown OK"); +const Service::SM::ServiceManager& System::ServiceManager() const { + return *service_manager; } } // namespace Core diff --git a/src/core/core.h b/src/core/core.h index ada23b347..f90f085ad 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -4,23 +4,34 @@ #pragma once +#include <array> #include <memory> #include <string> +#include <thread> #include "common/common_types.h" +#include "core/core_cpu.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/scheduler.h" #include "core/loader/loader.h" #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" class EmuWindow; class ARM_Interface; +namespace Service::SM { +class ServiceManager; +} + namespace Core { class System { public: + ~System(); + /** * Gets the instance of the System singleton class. * @returns Reference to the instance of the System singleton class. @@ -42,6 +53,7 @@ public: ErrorSystemFiles, ///< Error in finding system files ErrorSharedFont, ///< Error in finding shared font ErrorVideoCore, ///< Error in the video core + ErrorUnsupportedArch, ///< Unsupported Architecture (32-Bit ROMs) ErrorUnknown ///< Any other error }; @@ -52,10 +64,10 @@ public: * is not required to do a full dispatch with each instruction. NOTE: the number of instructions * requested is not guaranteed to run, as this will be interrupted preemptively if a hardware * update is requested (e.g. on a thread switch). - * @param tight_loop Number of instructions to execute. + * @param tight_loop If false, the CPU single-steps. * @return Result status, indicating whether or not the operation succeeded. */ - ResultStatus RunLoop(int tight_loop = 100000); + ResultStatus RunLoop(bool tight_loop = true); /** * Step the CPU one instruction @@ -80,7 +92,7 @@ public: * @returns True if the emulated system is powered on, otherwise false. */ bool IsPoweredOn() const { - return cpu_core != nullptr; + return cpu_barrier && cpu_barrier->IsAlive(); } /** @@ -94,22 +106,36 @@ public: /// Prepare the core emulation for a reschedule void PrepareReschedule(); + /// Gets and resets core performance statistics PerfStats::Results GetAndResetPerfStats(); - /** - * Gets a reference to the emulated CPU. - * @returns A reference to the emulated CPU. - */ - ARM_Interface& CPU() { - return *cpu_core; + /// Gets an ARM interface to the CPU core that is currently running + ARM_Interface& CurrentArmInterface() { + return CurrentCpuCore().ArmInterface(); } + /// Gets an ARM interface to the CPU core with the specified index + ARM_Interface& ArmInterface(size_t core_index); + + /// Gets a CPU interface to the CPU core with the specified index + Cpu& CpuCore(size_t core_index); + + /// Gets the GPU interface Tegra::GPU& GPU() { return *gpu_core; } - Kernel::Scheduler& Scheduler() { - return *scheduler; + /// Gets the scheduler for the CPU core that is currently running + Kernel::Scheduler& CurrentScheduler() { + return *CurrentCpuCore().Scheduler(); + } + + /// Gets the scheduler for the CPU core with the specified index + const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index); + + /// Gets the current process + Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { + return current_process; } PerfStats perf_stats; @@ -130,7 +156,21 @@ public: return *app_loader; } + Service::SM::ServiceManager& ServiceManager(); + const Service::SM::ServiceManager& ServiceManager() const; + + void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { + debug_context = std::move(context); + } + + std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const { + return debug_context; + } + private: + /// Returns the currently running CPU core + Cpu& CurrentCpuCore(); + /** * Initialize the emulated system. * @param emu_window Pointer to the host-system window used for video output and keyboard input. @@ -139,18 +179,18 @@ private: */ ResultStatus Init(EmuWindow* emu_window, u32 system_mode); - /// Reschedule the core emulation - void Reschedule(); - /// AppLoader used to load the current executing application std::unique_ptr<Loader::AppLoader> app_loader; - - std::shared_ptr<ARM_Interface> cpu_core; - std::unique_ptr<Kernel::Scheduler> scheduler; std::unique_ptr<Tegra::GPU> gpu_core; + std::shared_ptr<Tegra::DebugContext> debug_context; + Kernel::SharedPtr<Kernel::Process> current_process; + std::shared_ptr<CpuBarrier> cpu_barrier; + std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores; + std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; + size_t active_core{}; ///< Active core, only used in single thread mode - /// When true, signals that a reschedule should happen - bool reschedule_pending{}; + /// Service manager + std::shared_ptr<Service::SM::ServiceManager> service_manager; /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; @@ -159,14 +199,21 @@ private: ResultStatus status = ResultStatus::Success; std::string status_details = ""; + + /// Map of guest threads to CPU cores + std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu; }; -inline ARM_Interface& CPU() { - return System::GetInstance().CPU(); +inline ARM_Interface& CurrentArmInterface() { + return System::GetInstance().CurrentArmInterface(); } inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } +inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { + return System::GetInstance().CurrentProcess(); +} + } // namespace Core diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp new file mode 100644 index 000000000..099f2bb1a --- /dev/null +++ b/src/core/core_cpu.cpp @@ -0,0 +1,119 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <condition_variable> +#include <mutex> + +#include "common/logging/log.h" +#ifdef ARCHITECTURE_x86_64 +#include "core/arm/dynarmic/arm_dynarmic.h" +#endif +#include "core/arm/unicorn/arm_unicorn.h" +#include "core/core_cpu.h" +#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/thread.h" +#include "core/settings.h" + +namespace Core { + +void CpuBarrier::NotifyEnd() { + std::unique_lock<std::mutex> lock(mutex); + end = true; + condition.notify_all(); +} + +bool CpuBarrier::Rendezvous() { + if (!Settings::values.use_multi_core) { + // Meaningless when running in single-core mode + return true; + } + + if (!end) { + std::unique_lock<std::mutex> lock(mutex); + + --cores_waiting; + if (!cores_waiting) { + cores_waiting = NUM_CPU_CORES; + condition.notify_all(); + return true; + } + + condition.wait(lock); + return true; + } + + return false; +} + +Cpu::Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index) + : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} { + + if (Settings::values.use_cpu_jit) { +#ifdef ARCHITECTURE_x86_64 + arm_interface = std::make_shared<ARM_Dynarmic>(); +#else + cpu_core = std::make_shared<ARM_Unicorn>(); + NGLOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); +#endif + } else { + arm_interface = std::make_shared<ARM_Unicorn>(); + } + + scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get()); +} + +void Cpu::RunLoop(bool tight_loop) { + // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step + if (!cpu_barrier->Rendezvous()) { + // If rendezvous failed, session has been killed + return; + } + + // If we don't have a currently active thread then don't execute instructions, + // instead advance to the next event and try to yield to the next thread + if (Kernel::GetCurrentThread() == nullptr) { + NGLOG_TRACE(Core, "Core-{} idling", core_index); + + if (IsMainCore()) { + CoreTiming::Idle(); + CoreTiming::Advance(); + } + + PrepareReschedule(); + } else { + if (IsMainCore()) { + CoreTiming::Advance(); + } + + if (tight_loop) { + arm_interface->Run(); + } else { + arm_interface->Step(); + } + } + + Reschedule(); +} + +void Cpu::SingleStep() { + return RunLoop(false); +} + +void Cpu::PrepareReschedule() { + arm_interface->PrepareReschedule(); + reschedule_pending = true; +} + +void Cpu::Reschedule() { + if (!reschedule_pending) { + return; + } + + reschedule_pending = false; + scheduler->Reschedule(); +} + +} // namespace Core diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h new file mode 100644 index 000000000..243f0b5e7 --- /dev/null +++ b/src/core/core_cpu.h @@ -0,0 +1,78 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <string> +#include "common/common_types.h" + +class ARM_Interface; + +namespace Kernel { +class Scheduler; +} + +namespace Core { + +constexpr unsigned NUM_CPU_CORES{4}; + +class CpuBarrier { +public: + bool IsAlive() const { + return !end; + } + + void NotifyEnd(); + + bool Rendezvous(); + +private: + unsigned cores_waiting{NUM_CPU_CORES}; + std::mutex mutex; + std::condition_variable condition; + std::atomic<bool> end{}; +}; + +class Cpu { +public: + Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index); + + void RunLoop(bool tight_loop = true); + + void SingleStep(); + + void PrepareReschedule(); + + ARM_Interface& ArmInterface() { + return *arm_interface; + } + + const ARM_Interface& ArmInterface() const { + return *arm_interface; + } + + const std::shared_ptr<Kernel::Scheduler>& Scheduler() const { + return scheduler; + } + + bool IsMainCore() const { + return core_index == 0; + } + +private: + void Reschedule(); + + std::shared_ptr<ARM_Interface> arm_interface; + std::shared_ptr<CpuBarrier> cpu_barrier; + std::shared_ptr<Kernel::Scheduler> scheduler; + + bool reschedule_pending{}; + size_t core_index; +}; + +} // namespace Core diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 9e1bf2d0e..dc1d8668f 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -6,6 +6,7 @@ #include <algorithm> #include <cinttypes> +#include <limits> #include <mutex> #include <string> #include <tuple> @@ -57,7 +58,8 @@ static u64 event_fifo_id; // to the event_queue by the emu thread static Common::MPSCQueue<Event, false> ts_queue; -static constexpr int MAX_SLICE_LENGTH = 20000; +constexpr int MAX_SLICE_LENGTH = 20000; +constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE; static s64 idled_cycles; @@ -70,11 +72,59 @@ static EventType* ev_lost = nullptr; static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {} +s64 usToCycles(s64 us) { + if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { + NGLOG_ERROR(Core_Timing, "Integer overflow, use max value"); + return std::numeric_limits<s64>::max(); + } + if (us > MAX_VALUE_TO_MULTIPLY) { + NGLOG_DEBUG(Core_Timing, "Time very big, do rounding"); + return BASE_CLOCK_RATE * (us / 1000000); + } + return (BASE_CLOCK_RATE * us) / 1000000; +} + +s64 usToCycles(u64 us) { + if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { + NGLOG_ERROR(Core_Timing, "Integer overflow, use max value"); + return std::numeric_limits<s64>::max(); + } + if (us > MAX_VALUE_TO_MULTIPLY) { + NGLOG_DEBUG(Core_Timing, "Time very big, do rounding"); + return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000); + } + return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000; +} + +s64 nsToCycles(s64 ns) { + if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { + NGLOG_ERROR(Core_Timing, "Integer overflow, use max value"); + return std::numeric_limits<s64>::max(); + } + if (ns > MAX_VALUE_TO_MULTIPLY) { + NGLOG_DEBUG(Core_Timing, "Time very big, do rounding"); + return BASE_CLOCK_RATE * (ns / 1000000000); + } + return (BASE_CLOCK_RATE * ns) / 1000000000; +} + +s64 nsToCycles(u64 ns) { + if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { + NGLOG_ERROR(Core_Timing, "Integer overflow, use max value"); + return std::numeric_limits<s64>::max(); + } + if (ns > MAX_VALUE_TO_MULTIPLY) { + NGLOG_DEBUG(Core_Timing, "Time very big, do rounding"); + return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000); + } + return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000; +} + EventType* RegisterEvent(const std::string& name, TimedCallback callback) { // check for existing type with same name. // we want event type names to remain unique so that we can use them for serialization. ASSERT_MSG(event_types.find(name) == event_types.end(), - "CoreTiming Event \"%s\" is already registered. Events should only be registered " + "CoreTiming Event \"{}\" is already registered. Events should only be registered " "during Init to avoid breaking save states.", name.c_str()); diff --git a/src/core/core_timing.h b/src/core/core_timing.h index b9eb38ea4..dc31124a8 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -18,15 +18,14 @@ */ #include <functional> -#include <limits> #include <string> #include "common/common_types.h" -#include "common/logging/log.h" + +namespace CoreTiming { // The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz // The exact value used is of course unverified. constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked -constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE; inline s64 msToCycles(int ms) { // since ms is int there is no way to overflow @@ -49,29 +48,9 @@ inline s64 usToCycles(int us) { return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000); } -inline s64 usToCycles(s64 us) { - if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { - LOG_ERROR(Core_Timing, "Integer overflow, use max value"); - return std::numeric_limits<s64>::max(); - } - if (us > MAX_VALUE_TO_MULTIPLY) { - LOG_DEBUG(Core_Timing, "Time very big, do rounding"); - return BASE_CLOCK_RATE * (us / 1000000); - } - return (BASE_CLOCK_RATE * us) / 1000000; -} +s64 usToCycles(s64 us); -inline s64 usToCycles(u64 us) { - if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { - LOG_ERROR(Core_Timing, "Integer overflow, use max value"); - return std::numeric_limits<s64>::max(); - } - if (us > MAX_VALUE_TO_MULTIPLY) { - LOG_DEBUG(Core_Timing, "Time very big, do rounding"); - return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000); - } - return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000; -} +s64 usToCycles(u64 us); inline s64 nsToCycles(float ns) { return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns); @@ -81,29 +60,9 @@ inline s64 nsToCycles(int ns) { return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000; } -inline s64 nsToCycles(s64 ns) { - if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { - LOG_ERROR(Core_Timing, "Integer overflow, use max value"); - return std::numeric_limits<s64>::max(); - } - if (ns > MAX_VALUE_TO_MULTIPLY) { - LOG_DEBUG(Core_Timing, "Time very big, do rounding"); - return BASE_CLOCK_RATE * (ns / 1000000000); - } - return (BASE_CLOCK_RATE * ns) / 1000000000; -} +s64 nsToCycles(s64 ns); -inline s64 nsToCycles(u64 ns) { - if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { - LOG_ERROR(Core_Timing, "Integer overflow, use max value"); - return std::numeric_limits<s64>::max(); - } - if (ns > MAX_VALUE_TO_MULTIPLY) { - LOG_DEBUG(Core_Timing, "Time very big, do rounding"); - return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000); - } - return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000; -} +s64 nsToCycles(u64 ns); inline u64 cyclesToNs(s64 cycles) { return cycles * 1000000000 / BASE_CLOCK_RATE; @@ -117,8 +76,6 @@ inline u64 cyclesToMs(s64 cycles) { return cycles * 1000 / BASE_CLOCK_RATE; } -namespace CoreTiming { - /** * CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is * required to end slice -1 and start slice 0 before the first cycle of code is executed. diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 5a40bf472..c7639795e 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -6,34 +6,28 @@ #include <array> #include <cstddef> +#include "common/common_funcs.h" #include "common/common_types.h" +#include "core/file_sys/filesystem.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // FileSys namespace namespace FileSys { -// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format -const size_t FILENAME_LENGTH = 0x20C / 2; +// Structure of a directory entry, from +// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry +const size_t FILENAME_LENGTH = 0x300; struct Entry { - char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated) - std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated) - char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD) - std::array<char, 4> - extension; // 8.3 file extension (set to spaces for directories, null-terminated) - char unknown2; // unknown (always 0x01) - char unknown3; // unknown (0x00 or 0x08) - char is_directory; // directory flag - char is_hidden; // hidden flag - char is_archive; // archive flag - char is_read_only; // read-only flag - u64 file_size; // file size (for files only) + char filename[FILENAME_LENGTH]; + INSERT_PADDING_BYTES(4); + EntryType type; + INSERT_PADDING_BYTES(3); + u64 file_size; }; -static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!"); -static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry."); -static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry."); -static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry."); -static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry."); +static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!"); +static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry."); +static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry."); class DirectoryBackend : NonCopyable { public: @@ -46,7 +40,10 @@ public: * @param entries Buffer to read data into * @return Number of entries listed */ - virtual u32 Read(const u32 count, Entry* entries) = 0; + virtual u64 Read(const u64 count, Entry* entries) = 0; + + /// Returns the number of entries still left to read. + virtual u64 GetEntryCount() const = 0; /** * Close the directory diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp new file mode 100644 index 000000000..8aa0e0aa4 --- /dev/null +++ b/src/core/file_sys/disk_filesystem.cpp @@ -0,0 +1,237 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/errors.h" + +namespace FileSys { + +static std::string ModeFlagsToString(Mode mode) { + std::string mode_str; + u32 mode_flags = static_cast<u32>(mode); + + // Calculate the correct open mode for the file. + if ((mode_flags & static_cast<u32>(Mode::Read)) && + (mode_flags & static_cast<u32>(Mode::Write))) { + if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a+"; + else + mode_str = "r+"; + } else { + if (mode_flags & static_cast<u32>(Mode::Read)) + mode_str = "r"; + else if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a"; + else if (mode_flags & static_cast<u32>(Mode::Write)) + mode_str = "w"; + } + + mode_str += "b"; + + return mode_str; +} + +std::string Disk_FileSystem::GetName() const { + return "Disk"; +} + +ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path, + Mode mode) const { + + // Calculate the correct open mode for the file. + std::string mode_str = ModeFlagsToString(mode); + + std::string full_path = base_directory + path; + auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str()); + + if (!file->IsOpen()) { + return ERROR_PATH_NOT_FOUND; + } + + return MakeResult<std::unique_ptr<StorageBackend>>( + std::make_unique<Disk_Storage>(std::move(file))); +} + +ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const { + if (!FileUtil::Exists(path)) { + return ERROR_PATH_NOT_FOUND; + } + + FileUtil::Delete(path); + + return RESULT_SUCCESS; +} + +ResultCode Disk_FileSystem::RenameFile(const std::string& src_path, + const std::string& dest_path) const { + const std::string full_src_path = base_directory + src_path; + const std::string full_dest_path = base_directory + dest_path; + + if (!FileUtil::Exists(full_src_path)) { + return ERROR_PATH_NOT_FOUND; + } + // TODO(wwylele): Use correct error code + return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1); +} + +ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + + std::string full_path = base_directory + path; + if (size == 0) { + FileUtil::CreateEmptyFile(full_path); + return RESULT_SUCCESS; + } + + FileUtil::IOFile file(full_path, "wb"); + // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) + // We do this by seeking to the right size, then writing a single null byte. + if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { + return RESULT_SUCCESS; + } + + NGLOG_ERROR(Service_FS, "Too large file"); + // TODO(Subv): Find out the correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const { + // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox. + std::string full_path = base_directory + path; + + if (FileUtil::CreateDir(full_path)) { + return RESULT_SUCCESS; + } + + NGLOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory( + const std::string& path) const { + + std::string full_path = base_directory + path; + + if (!FileUtil::IsDirectory(full_path)) { + // TODO(Subv): Find the correct error code for this. + return ResultCode(-1); + } + + auto directory = std::make_unique<Disk_Directory>(full_path); + return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); +} + +u64 Disk_FileSystem::GetFreeSpaceSize() const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + return 0; +} + +ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const { + std::string full_path = base_directory + path; + if (!FileUtil::Exists(full_path)) { + return ERROR_PATH_NOT_FOUND; + } + + if (FileUtil::IsDirectory(full_path)) + return MakeResult(EntryType::Directory); + + return MakeResult(EntryType::File); +} + +ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { + NGLOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); + file->Seek(offset, SEEK_SET); + return MakeResult<size_t>(file->ReadBytes(buffer, length)); +} + +ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush, + const u8* buffer) const { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + file->Seek(offset, SEEK_SET); + size_t written = file->WriteBytes(buffer, length); + if (flush) { + file->Flush(); + } + return MakeResult<size_t>(written); +} + +u64 Disk_Storage::GetSize() const { + return file->GetSize(); +} + +bool Disk_Storage::SetSize(const u64 size) const { + file->Resize(size); + file->Flush(); + return true; +} + +Disk_Directory::Disk_Directory(const std::string& path) { + unsigned size = FileUtil::ScanDirectoryTree(path, directory); + directory.size = size; + directory.isDirectory = true; + children_iterator = directory.children.begin(); +} + +u64 Disk_Directory::Read(const u64 count, Entry* entries) { + u64 entries_read = 0; + + while (entries_read < count && children_iterator != directory.children.cend()) { + const FileUtil::FSTEntry& file = *children_iterator; + const std::string& filename = file.virtualName; + Entry& entry = entries[entries_read]; + + NGLOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory); + + // TODO(Link Mauve): use a proper conversion to UTF-16. + for (size_t j = 0; j < FILENAME_LENGTH; ++j) { + entry.filename[j] = filename[j]; + if (!filename[j]) + break; + } + + if (file.isDirectory) { + entry.file_size = 0; + entry.type = EntryType::Directory; + } else { + entry.file_size = file.size; + entry.type = EntryType::File; + } + + ++entries_read; + ++children_iterator; + } + return entries_read; +} + +u64 Disk_Directory::GetEntryCount() const { + // We convert the children iterator into a const_iterator to allow template argument deduction + // in std::distance. + std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator; + return std::distance(current, directory.children.end()); +} + +} // namespace FileSys diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h new file mode 100644 index 000000000..591e39fda --- /dev/null +++ b/src/core/file_sys/disk_filesystem.h @@ -0,0 +1,84 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> +#include <string> +#include "common/common_types.h" +#include "common/file_util.h" +#include "core/file_sys/directory.h" +#include "core/file_sys/filesystem.h" +#include "core/file_sys/storage.h" +#include "core/hle/result.h" + +namespace FileSys { + +class Disk_FileSystem : public FileSystemBackend { +public: + explicit Disk_FileSystem(std::string base_directory) + : base_directory(std::move(base_directory)) {} + + std::string GetName() const override; + + ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const override; + ResultCode DeleteFile(const std::string& path) const override; + ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; + ResultCode CreateFile(const std::string& path, u64 size) const override; + ResultCode CreateDirectory(const std::string& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; + u64 GetFreeSpaceSize() const override; + ResultVal<EntryType> GetEntryType(const std::string& path) const override; + +protected: + std::string base_directory; +}; + +class Disk_Storage : public StorageBackend { +public: + explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {} + + ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; + ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; + u64 GetSize() const override; + bool SetSize(u64 size) const override; + bool Close() const override { + return false; + } + void Flush() const override {} + +private: + std::shared_ptr<FileUtil::IOFile> file; +}; + +class Disk_Directory : public DirectoryBackend { +public: + explicit Disk_Directory(const std::string& path); + + ~Disk_Directory() override { + Close(); + } + + u64 Read(const u64 count, Entry* entries) override; + u64 GetEntryCount() const override; + + bool Close() const override { + return true; + } + +protected: + FileUtil::FSTEntry directory; + + // We need to remember the last entry we returned, so a subsequent call to Read will continue + // from the next one. This iterator will always point to the next unread entry. + std::vector<FileUtil::FSTEntry>::iterator children_iterator; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index be3224ef8..0ed7d2a0c 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -10,36 +10,17 @@ namespace FileSys { namespace ErrCodes { enum { - RomFSNotFound = 100, - ArchiveNotMounted = 101, - FileNotFound = 112, - PathNotFound = 113, - GameCardNotInserted = 141, - NotFound = 120, - FileAlreadyExists = 180, - DirectoryAlreadyExists = 185, - AlreadyExists = 190, - InvalidOpenFlags = 230, - DirectoryNotEmpty = 240, - NotAFile = 250, - NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive - ExeFSSectionNotFound = 567, - CommandNotAllowed = 630, - InvalidReadFlag = 700, - InvalidPath = 702, - WriteBeyondEnd = 705, - UnsupportedOpenFlags = 760, - IncorrectExeFSReadSize = 761, - UnexpectedFileOrDirectory = 770, + NotFound = 1, }; } +constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrorModule::FS, ErrCodes::NotFound); + // TODO(bunnei): Replace these with correct errors for Switch OS constexpr ResultCode ERROR_INVALID_PATH(ResultCode(-1)); constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ResultCode(-1)); constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(ResultCode(-1)); constexpr ResultCode ERROR_FILE_NOT_FOUND(ResultCode(-1)); -constexpr ResultCode ERROR_PATH_NOT_FOUND(ResultCode(-1)); constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ResultCode(-1)); constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ResultCode(-1)); constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(ResultCode(-1)); diff --git a/src/core/file_sys/filesystem.cpp b/src/core/file_sys/filesystem.cpp index 82fdb3c46..87083878b 100644 --- a/src/core/file_sys/filesystem.cpp +++ b/src/core/file_sys/filesystem.cpp @@ -71,7 +71,7 @@ std::string Path::AsString() const { case Binary: default: // TODO(yuriks): Add assert - LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!"); + NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to string!"); return {}; } } @@ -87,7 +87,7 @@ std::u16string Path::AsU16Str() const { case Invalid: case Binary: // TODO(yuriks): Add assert - LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!"); + NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!"); return {}; } @@ -115,7 +115,7 @@ std::vector<u8> Path::AsBinary() const { case Invalid: default: // TODO(yuriks): Add assert - LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!"); + NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!"); return {}; } } diff --git a/src/core/file_sys/filesystem.h b/src/core/file_sys/filesystem.h index 02705506b..295a3133e 100644 --- a/src/core/file_sys/filesystem.h +++ b/src/core/file_sys/filesystem.h @@ -27,11 +27,15 @@ enum LowPathType : u32 { Wchar = 4, }; -union Mode { - u32 hex; - BitField<0, 1, u32> read_flag; - BitField<1, 1, u32> write_flag; - BitField<2, 1, u32> create_flag; +enum EntryType : u8 { + Directory = 0, + File = 1, +}; + +enum class Mode : u32 { + Read = 1, + Write = 2, + Append = 4, }; class Path { @@ -86,21 +90,21 @@ public: * @param size The size of the new file, filled with zeroes * @return Result of the operation */ - virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; + virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0; /** * Delete a file specified by its path * @param path Path relative to the archive * @return Result of the operation */ - virtual ResultCode DeleteFile(const Path& path) const = 0; + virtual ResultCode DeleteFile(const std::string& path) const = 0; /** * Create a directory specified by its path * @param path Path relative to the archive * @return Result of the operation */ - virtual ResultCode CreateDirectory(const Path& path) const = 0; + virtual ResultCode CreateDirectory(const std::string& path) const = 0; /** * Delete a directory specified by its path @@ -122,7 +126,8 @@ public: * @param dest_path Destination path relative to the archive * @return Result of the operation */ - virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; + virtual ResultCode RenameFile(const std::string& src_path, + const std::string& dest_path) const = 0; /** * Rename a Directory specified by its path @@ -138,21 +143,28 @@ public: * @param mode Mode to open the file with * @return Opened file, or error code */ - virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const Path& path, - const Mode& mode) const = 0; + virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const = 0; /** * Open a directory specified by its path * @param path Path relative to the archive * @return Opened directory, or error code */ - virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; + virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const = 0; /** * Get the free space * @return The number of free bytes in the archive */ virtual u64 GetFreeSpaceSize() const = 0; + + /** + * Get the type of the specified path + * @return The type of the specified path or error code + */ + virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0; }; class FileSystemFactory : NonCopyable { @@ -174,10 +186,9 @@ public: /** * Deletes the archive contents and then re-creates the base folder * @param path Path to the archive - * @param format_info Format information for the new archive * @return ResultCode of the operation, 0 on success */ - virtual ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) = 0; + virtual ResultCode Format(const Path& path) = 0; /** * Retrieves the format info about the archive with the specified path diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp new file mode 100644 index 000000000..808254ecc --- /dev/null +++ b/src/core/file_sys/partition_filesystem.cpp @@ -0,0 +1,124 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <utility> +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/loader/loader.h" + +namespace FileSys { + +Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) { + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + // At least be as large as the header + if (file.GetSize() < sizeof(Header)) + return Loader::ResultStatus::Error; + + // For cartridges, HFSs can get very large, so we need to calculate the size up to + // the actual content itself instead of just blindly reading in the entire file. + Header pfs_header; + if (!file.ReadBytes(&pfs_header, sizeof(Header))) + return Loader::ResultStatus::Error; + + bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + size_t metadata_size = + sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; + + // Actually read in now... + file.Seek(offset, SEEK_SET); + std::vector<u8> file_data(metadata_size); + + if (!file.ReadBytes(file_data.data(), metadata_size)) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(file_data); + if (result != Loader::ResultStatus::Success) + NGLOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path); + + return result; +} + +Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) { + size_t total_size = file_data.size() - offset; + if (total_size < sizeof(Header)) + return Loader::ResultStatus::Error; + + memcpy(&pfs_header, &file_data[offset], sizeof(Header)); + is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + + size_t entries_offset = offset + sizeof(Header); + size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); + for (u16 i = 0; i < pfs_header.num_entries; i++) { + FileEntry entry; + + memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry)); + entry.name = std::string(reinterpret_cast<const char*>( + &file_data[strtab_offset + entry.fs_entry.strtab_offset])); + pfs_entries.push_back(std::move(entry)); + } + + content_offset = strtab_offset + pfs_header.strtab_size; + + return Loader::ResultStatus::Success; +} + +u32 PartitionFilesystem::GetNumEntries() const { + return pfs_header.num_entries; +} + +u64 PartitionFilesystem::GetEntryOffset(int index) const { + if (index > GetNumEntries()) + return 0; + + return content_offset + pfs_entries[index].fs_entry.offset; +} + +u64 PartitionFilesystem::GetEntrySize(int index) const { + if (index > GetNumEntries()) + return 0; + + return pfs_entries[index].fs_entry.size; +} + +std::string PartitionFilesystem::GetEntryName(int index) const { + if (index > GetNumEntries()) + return ""; + + return pfs_entries[index].name; +} + +u64 PartitionFilesystem::GetFileOffset(const std::string& name) const { + for (u32 i = 0; i < pfs_header.num_entries; i++) { + if (pfs_entries[i].name == name) + return content_offset + pfs_entries[i].fs_entry.offset; + } + + return 0; +} + +u64 PartitionFilesystem::GetFileSize(const std::string& name) const { + for (u32 i = 0; i < pfs_header.num_entries; i++) { + if (pfs_entries[i].name == name) + return pfs_entries[i].fs_entry.size; + } + + return 0; +} + +void PartitionFilesystem::Print() const { + NGLOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries); + for (u32 i = 0; i < pfs_header.num_entries; i++) { + NGLOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i, + pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size, + GetFileOffset(pfs_entries[i].name)); + } +} +} // namespace FileSys diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h new file mode 100644 index 000000000..573c90057 --- /dev/null +++ b/src/core/file_sys/partition_filesystem.h @@ -0,0 +1,87 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { +enum class ResultStatus; +} + +namespace FileSys { + +/** + * Helper which implements an interface to parse PFS/HFS filesystems. + * Data can either be loaded from a file path or data with an offset into it. + */ +class PartitionFilesystem { +public: + Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0); + Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0); + + u32 GetNumEntries() const; + u64 GetEntryOffset(int index) const; + u64 GetEntrySize(int index) const; + std::string GetEntryName(int index) const; + u64 GetFileOffset(const std::string& name) const; + u64 GetFileSize(const std::string& name) const; + + void Print() const; + +private: + struct Header { + std::array<char, 4> magic; + u32_le num_entries; + u32_le strtab_size; + INSERT_PADDING_BYTES(0x4); + }; + + static_assert(sizeof(Header) == 0x10, "PFS/HFS header structure size is wrong"); + +#pragma pack(push, 1) + struct FSEntry { + u64_le offset; + u64_le size; + u32_le strtab_offset; + }; + + static_assert(sizeof(FSEntry) == 0x14, "FS entry structure size is wrong"); + + struct PFSEntry { + FSEntry fs_entry; + INSERT_PADDING_BYTES(0x4); + }; + + static_assert(sizeof(PFSEntry) == 0x18, "PFS entry structure size is wrong"); + + struct HFSEntry { + FSEntry fs_entry; + u32_le hash_region_size; + INSERT_PADDING_BYTES(0x8); + std::array<char, 0x20> hash; + }; + + static_assert(sizeof(HFSEntry) == 0x40, "HFS entry structure size is wrong"); + +#pragma pack(pop) + + struct FileEntry { + FSEntry fs_entry; + std::string name; + }; + + Header pfs_header; + bool is_hfs; + size_t content_offset; + + std::vector<FileEntry> pfs_entries; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index a6dcebcc3..25a822891 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cinttypes> #include "common/file_util.h" #include "common/logging/log.h" #include "core/file_sys/program_metadata.h" @@ -22,7 +21,7 @@ Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) { Loader::ResultStatus result = Load(file_data); if (result != Loader::ResultStatus::Success) - LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str()); + NGLOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path); return result; } @@ -77,14 +76,14 @@ u64 ProgramMetadata::GetFilesystemPermissions() const { } void ProgramMetadata::Print() const { - LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data()); - LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority); - LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu); - LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size); - LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category); - LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags); - LOG_DEBUG(Service_FS, " > 64-bit instructions: %s", - npdm_header.has_64_bit_instructions ? "YES" : "NO"); + NGLOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority); + NGLOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu); + NGLOG_DEBUG(Service_FS, "Main thread stack size: 0x{:X} bytes", npdm_header.main_stack_size); + NGLOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category); + NGLOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags); + NGLOG_DEBUG(Service_FS, " > 64-bit instructions: {}", + npdm_header.has_64_bit_instructions ? "YES" : "NO"); auto address_space = "Unknown"; switch (npdm_header.address_space_type) { @@ -96,19 +95,19 @@ void ProgramMetadata::Print() const { break; } - LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space); + NGLOG_DEBUG(Service_FS, " > Address space: {}\n", address_space); // Begin ACID printing (potential perms, signed) - LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data()); - LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags); - LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO"); - LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min); - LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max); - LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions); + NGLOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags); + NGLOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO"); + NGLOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min); + NGLOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max); + NGLOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions); // Begin ACI0 printing (actual perms, unsigned) - LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data()); - LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id); - LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions); + NGLOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id); + NGLOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions); } } // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index e0de49f05..dc7591aca 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -14,7 +14,7 @@ namespace FileSys { RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) { // Load the RomFS from the app if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { - LOG_ERROR(Service_FS, "Unable to read RomFS!"); + NGLOG_ERROR(Service_FS, "Unable to read RomFS!"); } } @@ -23,14 +23,14 @@ ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& pa return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); } -ResultCode RomFS_Factory::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) { - LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str()); +ResultCode RomFS_Factory::Format(const Path& path) { + NGLOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName()); // TODO(bunnei): Find the right error code for this return ResultCode(-1); } ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const { - LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName()); // TODO(bunnei): Find the right error code for this return ResultCode(-1); } diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 10ea13966..e0698e642 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -23,7 +23,7 @@ public: return "ArchiveFactory_RomFS"; } ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; - ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; + ResultCode Format(const Path& path) override; ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; private: diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp index ca1463d7c..8e2bce687 100644 --- a/src/core/file_sys/romfs_filesystem.cpp +++ b/src/core/file_sys/romfs_filesystem.cpp @@ -14,73 +14,79 @@ std::string RomFS_FileSystem::GetName() const { return "RomFS"; } -ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const Path& path, - const Mode& mode) const { +ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path, + Mode mode) const { return MakeResult<std::unique_ptr<StorageBackend>>( std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size)); } -ResultCode RomFS_FileSystem::DeleteFile(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive (%s).", - GetName().c_str()); +ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const { + NGLOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName()); // TODO(bunnei): Use correct error code return ResultCode(-1); } -ResultCode RomFS_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).", - GetName().c_str()); +ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path, + const std::string& dest_path) const { + NGLOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", + GetName()); // TODO(wwylele): Use correct error code return ResultCode(-1); } ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).", - GetName().c_str()); + NGLOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", + GetName()); // TODO(wwylele): Use correct error code return ResultCode(-1); } ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).", - GetName().c_str()); + NGLOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", + GetName()); // TODO(wwylele): Use correct error code return ResultCode(-1); } -ResultCode RomFS_FileSystem::CreateFile(const Path& path, u64 size) const { - LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive (%s).", - GetName().c_str()); +ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const { + NGLOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName()); // TODO(bunnei): Use correct error code return ResultCode(-1); } -ResultCode RomFS_FileSystem::CreateDirectory(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).", - GetName().c_str()); +ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const { + NGLOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).", + GetName()); // TODO(wwylele): Use correct error code return ResultCode(-1); } ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).", - GetName().c_str()); + NGLOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", + GetName()); // TODO(wwylele): Use correct error code return ResultCode(-1); } ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory( - const Path& path) const { + const std::string& path) const { + NGLOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive"); return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>()); } u64 RomFS_FileSystem::GetFreeSpaceSize() const { - LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive"); + NGLOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive"); return 0; } +ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const { + NGLOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { - LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length); + NGLOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); romfs_file->Seek(data_offset + offset, SEEK_SET); size_t read_length = (size_t)std::min((u64)length, data_size - offset); @@ -89,7 +95,7 @@ ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush, const u8* buffer) const { - LOG_ERROR(Service_FS, "Attempted to write to ROMFS file"); + NGLOG_ERROR(Service_FS, "Attempted to write to ROMFS file"); // TODO(Subv): Find error code return MakeResult<size_t>(0); } @@ -99,7 +105,7 @@ u64 RomFS_Storage::GetSize() const { } bool RomFS_Storage::SetSize(const u64 size) const { - LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file"); + NGLOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file"); return false; } diff --git a/src/core/file_sys/romfs_filesystem.h b/src/core/file_sys/romfs_filesystem.h index 900ea567a..ba9d85823 100644 --- a/src/core/file_sys/romfs_filesystem.h +++ b/src/core/file_sys/romfs_filesystem.h @@ -29,17 +29,19 @@ public: std::string GetName() const override; - ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const Path& path, - const Mode& mode) const override; - ResultCode DeleteFile(const Path& path) const override; - ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const override; + ResultCode DeleteFile(const std::string& path) const override; + ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override; ResultCode DeleteDirectory(const Path& path) const override; ResultCode DeleteDirectoryRecursively(const Path& path) const override; - ResultCode CreateFile(const Path& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; + ResultCode CreateFile(const std::string& path, u64 size) const override; + ResultCode CreateDirectory(const std::string& path) const override; ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; u64 GetFreeSpaceSize() const override; + ResultVal<EntryType> GetEntryType(const std::string& path) const override; protected: std::shared_ptr<FileUtil::IOFile> romfs_file; @@ -69,7 +71,10 @@ private: class ROMFSDirectory : public DirectoryBackend { public: - u32 Read(const u32 count, Entry* entries) override { + u64 Read(const u64 count, Entry* entries) override { + return 0; + } + u64 GetEntryCount() const override { return 0; } bool Close() const override { diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp new file mode 100644 index 000000000..c1be8fee4 --- /dev/null +++ b/src/core/file_sys/savedata_factory.cpp @@ -0,0 +1,54 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/savedata_factory.h" +#include "core/hle/kernel/process.h" + +namespace FileSys { + +SaveData_Factory::SaveData_Factory(std::string nand_directory) + : nand_directory(std::move(nand_directory)) {} + +ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) { + std::string save_directory = GetFullPath(); + // Return an error if the save data doesn't actually exist. + if (!FileUtil::IsDirectory(save_directory)) { + // TODO(Subv): Find out correct error code. + return ResultCode(-1); + } + + auto archive = std::make_unique<Disk_FileSystem>(save_directory); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode SaveData_Factory::Format(const Path& path) { + NGLOG_WARNING(Service_FS, "Format archive {}", GetName()); + // Create the save data directory. + if (!FileUtil::CreateFullPath(GetFullPath())) { + // TODO(Subv): Find the correct error code. + return ResultCode(-1); + } + + return RESULT_SUCCESS; +} + +ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const { + NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +std::string SaveData_Factory::GetFullPath() const { + u64 title_id = Core::CurrentProcess()->program_id; + // TODO(Subv): Somehow obtain this value. + u32 user = 0; + return fmt::format("{}save/{:016X}/{:08X}/", nand_directory, title_id, user); +} + +} // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h new file mode 100644 index 000000000..73a42aab6 --- /dev/null +++ b/src/core/file_sys/savedata_factory.h @@ -0,0 +1,33 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" + +namespace FileSys { + +/// File system interface to the SaveData archive +class SaveData_Factory final : public FileSystemFactory { +public: + explicit SaveData_Factory(std::string nand_directory); + + std::string GetName() const override { + return "SaveData_Factory"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::string nand_directory; + + std::string GetFullPath() const; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp new file mode 100644 index 000000000..59ac3e0be --- /dev/null +++ b/src/core/file_sys/sdmc_factory.cpp @@ -0,0 +1,39 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/sdmc_factory.h" + +namespace FileSys { + +SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {} + +ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) { + // Create the SD Card directory if it doesn't already exist. + if (!FileUtil::IsDirectory(sd_directory)) { + FileUtil::CreateFullPath(sd_directory); + } + + auto archive = std::make_unique<Disk_FileSystem>(sd_directory); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode SDMC_Factory::Format(const Path& path) { + NGLOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName()); + // TODO(Subv): Find the right error code for this + return ResultCode(-1); +} + +ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const { + NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +} // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h new file mode 100644 index 000000000..93becda25 --- /dev/null +++ b/src/core/file_sys/sdmc_factory.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" + +namespace FileSys { + +/// File system interface to the SDCard archive +class SDMC_Factory final : public FileSystemFactory { +public: + explicit SDMC_Factory(std::string sd_directory); + + std::string GetName() const override { + return "SDMC_Factory"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::string sd_directory; +}; + +} // namespace FileSys diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 8c256beb5..79e52488f 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -59,7 +59,7 @@ template <typename InputDeviceType> void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) { auto pair = std::make_pair(name, std::move(factory)); if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) { - LOG_ERROR(Input, "Factory %s already registered", name.c_str()); + NGLOG_ERROR(Input, "Factory '{}' already registered", name); } } @@ -71,7 +71,7 @@ void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDevic template <typename InputDeviceType> void UnregisterFactory(const std::string& name) { if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) { - LOG_ERROR(Input, "Factory %s not registered", name.c_str()); + NGLOG_ERROR(Input, "Factory '{}' not registered", name); } } @@ -88,7 +88,7 @@ std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) { const auto pair = factory_list.find(engine); if (pair == factory_list.end()) { if (engine != "null") { - LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str()); + NGLOG_ERROR(Input, "Unknown engine name: {}", engine); } return std::make_unique<InputDeviceType>(); } diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 7a142dc21..2603192fe 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -6,7 +6,6 @@ #include <algorithm> #include <atomic> -#include <cinttypes> #include <climits> #include <csignal> #include <cstdarg> @@ -33,9 +32,13 @@ #include "common/logging/log.h" #include "common/string_util.h" +#include "common/swap.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/core_cpu.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/scheduler.h" #include "core/loader/loader.h" #include "core/memory.h" @@ -138,15 +141,17 @@ static u8 command_buffer[GDB_BUFFER_SIZE]; static u32 command_length; static u32 latest_signal = 0; -static bool step_break = false; static bool memory_break = false; +static Kernel::Thread* current_thread = nullptr; + // Binding to a port within the reserved ports range (0-1023) requires root permissions, // so default to a port outside of that range. static u16 gdbstub_port = 24689; static bool halt_loop = true; static bool step_loop = false; +static bool send_trap = false; // If set to false, the server will never be started and no // gdbstub-related functions will be executed. @@ -166,6 +171,53 @@ static std::map<u64, Breakpoint> breakpoints_execute; static std::map<u64, Breakpoint> breakpoints_read; static std::map<u64, Breakpoint> breakpoints_write; +static Kernel::Thread* FindThreadById(int id) { + for (int core = 0; core < Core::NUM_CPU_CORES; core++) { + auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); + for (auto thread : threads) { + if (thread->GetThreadId() == id) { + current_thread = thread.get(); + return current_thread; + } + } + } + return nullptr; +} + +static u64 RegRead(int id, Kernel::Thread* thread = nullptr) { + if (!thread) { + return 0; + } + + if (id < SP_REGISTER) { + return thread->context.cpu_registers[id]; + } else if (id == SP_REGISTER) { + return thread->context.sp; + } else if (id == PC_REGISTER) { + return thread->context.pc; + } else if (id == CPSR_REGISTER) { + return thread->context.cpsr; + } else { + return 0; + } +} + +static void RegWrite(int id, u64 val, Kernel::Thread* thread = nullptr) { + if (!thread) { + return; + } + + if (id < SP_REGISTER) { + thread->context.cpu_registers[id] = val; + } else if (id == SP_REGISTER) { + thread->context.sp = val; + } else if (id == PC_REGISTER) { + thread->context.pc = val; + } else if (id == CPSR_REGISTER) { + thread->context.cpsr = val; + } +} + /** * Turns hex string character into the equivalent byte. * @@ -180,7 +232,7 @@ static u8 HexCharToValue(u8 hex) { return hex - 'A' + 0xA; } - LOG_ERROR(Debug_GDBStub, "Invalid nibble: %c (%02x)\n", hex, hex); + NGLOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex); return 0; } @@ -194,7 +246,7 @@ static u8 NibbleToHex(u8 n) { if (n < 0xA) { return '0' + n; } else { - return 'A' + n - 0xA; + return 'a' + n - 0xA; } } @@ -320,7 +372,7 @@ static u8 ReadByte() { u8 c; size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); if (received_size != 1) { - LOG_ERROR(Debug_GDBStub, "recv failed : %ld", received_size); + NGLOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size); Shutdown(); } @@ -361,9 +413,8 @@ static void RemoveBreakpoint(BreakpointType type, PAddr addr) { auto bp = p.find(static_cast<u64>(addr)); if (bp != p.end()) { - LOG_DEBUG(Debug_GDBStub, - "gdb: removed a breakpoint: %016" PRIx64 " bytes at %016" PRIx64 " of type %d\n", - bp->second.len, bp->second.addr, static_cast<int>(type)); + NGLOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}", + bp->second.len, bp->second.addr, static_cast<int>(type)); p.erase(static_cast<u64>(addr)); } } @@ -408,10 +459,10 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) { } if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { - LOG_DEBUG(Debug_GDBStub, - "Found breakpoint type %d @ %016" PRIx64 ", range: %016" PRIx64 - " - %016" PRIx64 " (%" PRIx64 " bytes)\n", - static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len); + NGLOG_DEBUG(Debug_GDBStub, + "Found breakpoint type {} @ {:016X}, range: {:016X}" + " - {:016X} ({:X} bytes)", + static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len); return true; } } @@ -427,7 +478,7 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) { static void SendPacket(const char packet) { size_t sent_size = send(gdbserver_socket, &packet, 1, 0); if (sent_size != 1) { - LOG_ERROR(Debug_GDBStub, "send failed"); + NGLOG_ERROR(Debug_GDBStub, "send failed"); } } @@ -441,11 +492,13 @@ static void SendReply(const char* reply) { return; } + NGLOG_DEBUG(Debug_GDBStub, "Reply: {}", reply); + memset(command_buffer, 0, sizeof(command_buffer)); command_length = static_cast<u32>(strlen(reply)); if (command_length + 4 > sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); + NGLOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); return; } @@ -462,7 +515,7 @@ static void SendReply(const char* reply) { while (left > 0) { int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0); if (sent_size < 0) { - LOG_ERROR(Debug_GDBStub, "gdb: send failed"); + NGLOG_ERROR(Debug_GDBStub, "gdb: send failed"); return Shutdown(); } @@ -473,7 +526,7 @@ static void SendReply(const char* reply) { /// Handle query command from gdb client. static void HandleQuery() { - LOG_DEBUG(Debug_GDBStub, "gdb: query '%s'\n", command_buffer + 1); + NGLOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1); const char* query = reinterpret_cast<const char*>(command_buffer + 1); @@ -485,6 +538,22 @@ static void HandleQuery() { } else if (strncmp(query, "Xfer:features:read:target.xml:", strlen("Xfer:features:read:target.xml:")) == 0) { SendReply(target_xml); + } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { + std::string buffer = fmt::format("TextSeg={:0x}", Memory::PROCESS_IMAGE_VADDR); + SendReply(buffer.c_str()); + } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { + std::string val = "m"; + for (int core = 0; core < Core::NUM_CPU_CORES; core++) { + auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); + for (auto thread : threads) { + val += fmt::format("{:x}", thread->GetThreadId()); + val += ","; + } + } + val.pop_back(); + SendReply(val.c_str()); + } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { + SendReply("l"); } else { SendReply(""); } @@ -492,11 +561,40 @@ static void HandleQuery() { /// Handle set thread command from gdb client. static void HandleSetThread() { - if (memcmp(command_buffer, "Hg0", 3) == 0 || memcmp(command_buffer, "Hc-1", 4) == 0 || - memcmp(command_buffer, "Hc0", 4) == 0 || memcmp(command_buffer, "Hc1", 4) == 0) { - return SendReply("OK"); + if (memcmp(command_buffer, "Hc", 2) == 0 || memcmp(command_buffer, "Hg", 2) == 0) { + int thread_id = -1; + if (command_buffer[2] != '-') { + thread_id = static_cast<int>(HexToInt( + command_buffer + 2, + command_length - 2 /*strlen(reinterpret_cast<char*>(command_buffer) + 2)*/)); + } + if (thread_id >= 1) { + current_thread = FindThreadById(thread_id); + } + if (!current_thread) { + thread_id = 1; + current_thread = FindThreadById(thread_id); + } + if (current_thread) { + SendReply("OK"); + return; + } } + SendReply("E01"); +} +/// Handle thread alive command from gdb client. +static void HandleThreadAlive() { + int thread_id = static_cast<int>( + HexToInt(command_buffer + 1, + command_length - 1 /*strlen(reinterpret_cast<char*>(command_buffer) + 1)*/)); + if (thread_id == 0) { + thread_id = 1; + } + if (FindThreadById(thread_id)) { + SendReply("OK"); + return; + } SendReply("E01"); } @@ -505,15 +603,24 @@ static void HandleSetThread() { * * @param signal Signal to be sent to client. */ -static void SendSignal(u32 signal) { +static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) { if (gdbserver_socket == -1) { return; } latest_signal = signal; - std::string buffer = Common::StringFromFormat("T%02x", latest_signal); - LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str()); + std::string buffer; + if (full) { + buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};", latest_signal, PC_REGISTER, + Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER, + Common::swap64(RegRead(SP_REGISTER, thread))); + } else { + buffer = fmt::format("T{:02x};", latest_signal); + } + + buffer += fmt::format("thread:{:x};", thread->GetThreadId()); + SendReply(buffer.c_str()); } @@ -527,18 +634,18 @@ static void ReadCommand() { // ignore ack return; } else if (c == 0x03) { - LOG_INFO(Debug_GDBStub, "gdb: found break command\n"); + NGLOG_INFO(Debug_GDBStub, "gdb: found break command"); halt_loop = true; - SendSignal(SIGTRAP); + SendSignal(current_thread, SIGTRAP); return; } else if (c != GDB_STUB_START) { - LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte %02x\n", c); + NGLOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c); return; } while ((c = ReadByte()) != GDB_STUB_END) { if (command_length >= sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow\n"); + NGLOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow"); SendPacket(GDB_STUB_NACK); return; } @@ -551,9 +658,10 @@ static void ReadCommand() { u8 checksum_calculated = CalculateChecksum(command_buffer, command_length); if (checksum_received != checksum_calculated) { - LOG_ERROR(Debug_GDBStub, - "gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)\n", - checksum_calculated, checksum_received, command_buffer, command_length); + NGLOG_ERROR( + Debug_GDBStub, + "gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})", + checksum_calculated, checksum_received, command_buffer, command_length); command_length = 0; @@ -580,7 +688,7 @@ static bool IsDataAvailable() { t.tv_usec = 0; if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) { - LOG_ERROR(Debug_GDBStub, "select failed"); + NGLOG_ERROR(Debug_GDBStub, "select failed"); return false; } @@ -599,11 +707,11 @@ static void ReadRegister() { } if (id <= SP_REGISTER) { - LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id))); + LongToGdbHex(reply, RegRead(id, current_thread)); } else if (id == PC_REGISTER) { - LongToGdbHex(reply, Core::CPU().GetPC()); + LongToGdbHex(reply, RegRead(id, current_thread)); } else if (id == CPSR_REGISTER) { - IntToGdbHex(reply, Core::CPU().GetCPSR()); + IntToGdbHex(reply, (u32)RegRead(id, current_thread)); } else { return SendReply("E01"); } @@ -619,16 +727,16 @@ static void ReadRegisters() { u8* bufptr = buffer; for (int reg = 0; reg <= SP_REGISTER; reg++) { - LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg)); + LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread)); } bufptr += (32 * 16); - LongToGdbHex(bufptr, Core::CPU().GetPC()); + LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread)); bufptr += 16; - IntToGdbHex(bufptr, Core::CPU().GetCPSR()); + IntToGdbHex(bufptr, (u32)RegRead(CPSR_REGISTER, current_thread)); bufptr += 8; @@ -647,11 +755,11 @@ static void WriteRegister() { } if (id <= SP_REGISTER) { - Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr)); + RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); } else if (id == PC_REGISTER) { - Core::CPU().SetPC(GdbHexToLong(buffer_ptr)); + RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); } else if (id == CPSR_REGISTER) { - Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr)); + RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); } else { return SendReply("E01"); } @@ -668,11 +776,11 @@ static void WriteRegisters() { for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) { if (reg <= SP_REGISTER) { - Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16)); + RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else if (reg == PC_REGISTER) { - Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16)); + RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else if (reg == CPSR_REGISTER) { - Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16)); + RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread); } else { UNIMPLEMENTED(); } @@ -693,7 +801,7 @@ static void ReadMemory() { u64 len = HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset)); - LOG_DEBUG(Debug_GDBStub, "gdb: addr: %016llx len: %016llx\n", addr, len); + NGLOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len); if (len * 2 > sizeof(reply)) { SendReply("E01"); @@ -735,7 +843,7 @@ static void WriteMemory() { void Break(bool is_memory_break) { if (!halt_loop) { halt_loop = true; - SendSignal(SIGTRAP); + send_trap = true; } memory_break = is_memory_break; @@ -745,10 +853,10 @@ void Break(bool is_memory_break) { static void Step() { step_loop = true; halt_loop = true; - step_break = true; - SendSignal(SIGTRAP); + send_trap = true; } +/// Tell the CPU if we hit a memory breakpoint. bool IsMemoryBreak() { if (IsConnected()) { return false; @@ -760,7 +868,6 @@ bool IsMemoryBreak() { /// Tell the CPU to continue executing. static void Continue() { memory_break = false; - step_break = false; step_loop = false; halt_loop = false; } @@ -781,8 +888,8 @@ static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) { breakpoint.len = len; p.insert({addr, breakpoint}); - LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %016" PRIx64 " bytes at %016" PRIx64 "\n", - static_cast<int>(type), breakpoint.len, breakpoint.addr); + NGLOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}", + static_cast<int>(type), breakpoint.len, breakpoint.addr); return true; } @@ -889,7 +996,7 @@ void HandlePacket() { return; } - LOG_DEBUG(Debug_GDBStub, "Packet: %s", command_buffer); + NGLOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer); switch (command_buffer[0]) { case 'q': @@ -899,11 +1006,11 @@ void HandlePacket() { HandleSetThread(); break; case '?': - SendSignal(latest_signal); + SendSignal(current_thread, latest_signal); break; case 'k': Shutdown(); - LOG_INFO(Debug_GDBStub, "killed by gdb"); + NGLOG_INFO(Debug_GDBStub, "killed by gdb"); return; case 'g': ReadRegisters(); @@ -936,6 +1043,9 @@ void HandlePacket() { case 'Z': AddBreakpoint(); break; + case 'T': + HandleThreadAlive(); + break; default: SendReply(""); break; @@ -982,7 +1092,7 @@ static void Init(u16 port) { breakpoints_write.clear(); // Start gdb server - LOG_INFO(Debug_GDBStub, "Starting GDB server on port %d...", port); + NGLOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port); sockaddr_in saddr_server = {}; saddr_server.sin_family = AF_INET; @@ -995,28 +1105,28 @@ static void Init(u16 port) { int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0)); if (tmpsock == -1) { - LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); + NGLOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); } // Set socket to SO_REUSEADDR so it can always bind on the same port int reuse_enabled = 1; if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, sizeof(reuse_enabled)) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); + NGLOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); } const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server); socklen_t server_addrlen = sizeof(saddr_server); if (bind(tmpsock, server_addr, server_addrlen) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); + NGLOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); } if (listen(tmpsock, 1) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); + NGLOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); } // Wait for gdb to connect - LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n"); + NGLOG_INFO(Debug_GDBStub, "Waiting for gdb to connect..."); sockaddr_in saddr_client; sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); socklen_t client_addrlen = sizeof(saddr_client); @@ -1027,9 +1137,9 @@ static void Init(u16 port) { halt_loop = false; step_loop = false; - LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); + NGLOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); } else { - LOG_INFO(Debug_GDBStub, "Client connected.\n"); + NGLOG_INFO(Debug_GDBStub, "Client connected."); saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); } @@ -1048,7 +1158,7 @@ void Shutdown() { return; } - LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); + NGLOG_INFO(Debug_GDBStub, "Stopping GDB ..."); if (gdbserver_socket != -1) { shutdown(gdbserver_socket, SHUT_RDWR); gdbserver_socket = -1; @@ -1058,7 +1168,7 @@ void Shutdown() { WSACleanup(); #endif - LOG_INFO(Debug_GDBStub, "GDB stopped."); + NGLOG_INFO(Debug_GDBStub, "GDB stopped."); } bool IsServerEnabled() { @@ -1080,4 +1190,11 @@ bool GetCpuStepFlag() { void SetCpuStepFlag(bool is_step) { step_loop = is_step; } + +void SendTrap(Kernel::Thread* thread, int trap) { + if (send_trap) { + send_trap = false; + SendSignal(thread, trap); + } +} }; // namespace GDBStub diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h index 201fca095..f2418c9e4 100644 --- a/src/core/gdbstub/gdbstub.h +++ b/src/core/gdbstub/gdbstub.h @@ -7,6 +7,7 @@ #pragma once #include "common/common_types.h" +#include "core/hle/kernel/thread.h" namespace GDBStub { @@ -91,4 +92,12 @@ bool GetCpuStepFlag(); * @param is_step */ void SetCpuStepFlag(bool is_step); + +/** + * Send trap signal from thread back to the gdbstub server. + * + * @param thread Sending thread. + * @param trap Trap no. + */ +void SendTrap(Kernel::Thread* thread, int trap); } // namespace GDBStub diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index a6602e12c..eaa5395ac 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -29,9 +29,14 @@ enum class ControlCommand : u32 { }; enum class CommandType : u32 { + Invalid = 0, + LegacyRequest = 1, Close = 2, + LegacyControl = 3, Request = 4, Control = 5, + RequestWithContext = 6, + ControlWithContext = 7, Unspecified, }; @@ -167,6 +172,7 @@ struct DomainMessageHeader { struct { union { BitField<0, 8, CommandType> command; + BitField<8, 8, u32_le> input_object_count; BitField<16, 16, u32_le> size; }; u32_le object_id; diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 3f87c4297..24605a273 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -298,6 +298,13 @@ public: template <typename T> Kernel::SharedPtr<T> GetCopyObject(size_t index); + + template <class T> + std::shared_ptr<T> PopIpcInterface() { + ASSERT(context->Session()->IsDomain()); + ASSERT(context->GetDomainMessageHeader()->input_object_count > 0); + return context->GetDomainRequestHandler<T>(Pop<u32>() - 1); + } }; /// Pop /// diff --git a/src/core/hle/kernel/condition_variable.cpp b/src/core/hle/kernel/condition_variable.cpp deleted file mode 100644 index a786d7f74..000000000 --- a/src/core/hle/kernel/condition_variable.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "core/hle/kernel/condition_variable.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/object_address_table.h" -#include "core/hle/kernel/thread.h" - -namespace Kernel { - -ConditionVariable::ConditionVariable() {} -ConditionVariable::~ConditionVariable() {} - -ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr, - std::string name) { - SharedPtr<ConditionVariable> condition_variable(new ConditionVariable); - - condition_variable->name = std::move(name); - condition_variable->guest_addr = guest_addr; - condition_variable->mutex_addr = 0; - - // Condition variables are referenced by guest address, so track this in the kernel - g_object_address_table.Insert(guest_addr, condition_variable); - - return MakeResult<SharedPtr<ConditionVariable>>(std::move(condition_variable)); -} - -bool ConditionVariable::ShouldWait(Thread* thread) const { - return GetAvailableCount() <= 0; -} - -void ConditionVariable::Acquire(Thread* thread) { - if (GetAvailableCount() <= 0) - return; - - SetAvailableCount(GetAvailableCount() - 1); -} - -ResultCode ConditionVariable::Release(s32 target) { - if (target == -1) { - // When -1, wake up all waiting threads - SetAvailableCount(static_cast<s32>(GetWaitingThreads().size())); - WakeupAllWaitingThreads(); - } else { - // Otherwise, wake up just a single thread - SetAvailableCount(target); - WakeupWaitingThread(GetHighestPriorityReadyThread()); - } - - return RESULT_SUCCESS; -} - -s32 ConditionVariable::GetAvailableCount() const { - return Memory::Read32(guest_addr); -} - -void ConditionVariable::SetAvailableCount(s32 value) const { - Memory::Write32(guest_addr, value); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/condition_variable.h b/src/core/hle/kernel/condition_variable.h deleted file mode 100644 index 1c9f06769..000000000 --- a/src/core/hle/kernel/condition_variable.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <queue> -#include "common/common_types.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/wait_object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class ConditionVariable final : public WaitObject { -public: - /** - * Creates a condition variable. - * @param guest_addr Address of the object tracking the condition variable in guest memory. If - * specified, this condition variable will update the guest object when its state changes. - * @param name Optional name of condition variable. - * @return The created condition variable. - */ - static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr, - std::string name = "Unknown"); - - std::string GetTypeName() const override { - return "ConditionVariable"; - } - std::string GetName() const override { - return name; - } - - static const HandleType HANDLE_TYPE = HandleType::ConditionVariable; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - s32 GetAvailableCount() const; - void SetAvailableCount(s32 value) const; - - std::string name; ///< Name of condition variable (optional) - VAddr guest_addr; ///< Address of the guest condition variable value - VAddr mutex_addr; ///< (optional) Address of guest mutex value associated with this condition - ///< variable, used for implementing events - - bool ShouldWait(Thread* thread) const override; - void Acquire(Thread* thread) override; - - /** - * Releases a slot from a condition variable. - * @param target The number of threads to wakeup, -1 is all. - * @return ResultCode indicating if the operation succeeded. - */ - ResultCode Release(s32 target); - -private: - ConditionVariable(); - ~ConditionVariable() override; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index 29d8dfdaa..e1b5430bf 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -20,7 +20,10 @@ enum { MaxConnectionsReached = 52, // Confirmed Switch OS error codes + MisalignedAddress = 102, + InvalidProcessorId = 113, InvalidHandle = 114, + InvalidCombination = 116, Timeout = 117, SynchronizationCanceled = 118, TooLarge = 119, diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index 3beb55753..f7a9920d8 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -5,6 +5,7 @@ #include <utility> #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" @@ -25,7 +26,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { u16 slot = next_free_slot; if (slot >= generations.size()) { - LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); + NGLOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); return ERR_OUT_OF_HANDLES; } next_free_slot = generations[slot]; @@ -47,7 +48,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { ResultVal<Handle> HandleTable::Duplicate(Handle handle) { SharedPtr<Object> object = GetGeneric(handle); if (object == nullptr) { - LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); + NGLOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle); return ERR_INVALID_HANDLE; } return Create(std::move(object)); @@ -77,7 +78,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { return GetCurrentThread(); } else if (handle == CurrentProcess) { - return g_current_process; + return Core::CurrentProcess(); } if (!IsValid(handle)) { diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index d9faf4b53..01904467e 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -7,6 +7,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" @@ -26,6 +27,32 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s boost::range::remove_erase(connected_sessions, server_session); } +SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, + const std::string& reason, u64 timeout, + WakeupCallback&& callback) { + + // Put the client thread to sleep until the wait event is signaled or the timeout expires. + thread->wakeup_callback = + [context = *this, callback](ThreadWakeupReason reason, SharedPtr<Thread> thread, + SharedPtr<WaitObject> object, size_t index) mutable -> bool { + ASSERT(thread->status == THREADSTATUS_WAIT_HLE_EVENT); + callback(thread, context, reason); + context.WriteToOutgoingCommandBuffer(*thread); + return true; + }; + + auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); + thread->status = THREADSTATUS_WAIT_HLE_EVENT; + thread->wait_objects = {event}; + event->AddWaitingThread(thread); + + if (timeout > 0) { + thread->WakeAfterDelay(timeout); + } + + return event; +} + HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session) : server_session(std::move(server_session)) { cmd_buf[0] = 0; @@ -35,7 +62,7 @@ HLERequestContext::~HLERequestContext() = default; void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { IPC::RequestParser rp(src_cmdbuf); - command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); + command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); if (command_header->type == IPC::CommandType::Close) { // Close does not populate the rest of the IPC header @@ -45,7 +72,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { // If handle descriptor is present, add size of it if (command_header->enable_handle_descriptor) { handle_descriptor_header = - std::make_unique<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>()); + std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>()); if (handle_descriptor_header->send_current_pid) { rp.Skip(2, false); } @@ -83,20 +110,22 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { // Padding to align to 16 bytes rp.AlignWithPadding(); - if (Session()->IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) { + if (Session()->IsDomain() && ((command_header->type == IPC::CommandType::Request || + command_header->type == IPC::CommandType::RequestWithContext) || + !incoming)) { // If this is an incoming message, only CommandType "Request" has a domain header // All outgoing domain messages have the domain header, if only incoming has it if (incoming || domain_message_header) { domain_message_header = - std::make_unique<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>()); + std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>()); } else { if (Session()->IsDomain()) - LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!"); + NGLOG_WARNING(IPC, "Domain request has no DomainMessageHeader!"); } } data_payload_header = - std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); + std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); data_payload_offset = rp.GetCurrentOffset(); @@ -159,8 +188,11 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb return RESULT_SUCCESS; } -ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, - HandleTable& dst_table) { +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) { + std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; + Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), + dst_cmdbuf.size() * sizeof(u32)); + // The header was already built in the internal command buffer. Attempt to parse it to verify // the integrity and then copy it over to the target command buffer. ParseCommandBuffer(cmd_buf.data(), false); @@ -171,7 +203,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P if (domain_message_header) size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); - std::copy_n(cmd_buf.begin(), size, dst_cmdbuf); + std::copy_n(cmd_buf.begin(), size, dst_cmdbuf.data()); if (command_header->enable_handle_descriptor) { ASSERT_MSG(!move_objects.empty() || !copy_objects.empty(), @@ -213,50 +245,63 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size()); } } + + // Copy the translated command buffer back into the thread's command buffer area. + Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), + dst_cmdbuf.size() * sizeof(u32)); + return RESULT_SUCCESS; } -std::vector<u8> HLERequestContext::ReadBuffer() const { +std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const { std::vector<u8> buffer; - const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()}; + const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()}; if (is_buffer_a) { - buffer.resize(BufferDescriptorA()[0].Size()); - Memory::ReadBlock(BufferDescriptorA()[0].Address(), buffer.data(), buffer.size()); + buffer.resize(BufferDescriptorA()[buffer_index].Size()); + Memory::ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), + buffer.size()); } else { - buffer.resize(BufferDescriptorX()[0].Size()); - Memory::ReadBlock(BufferDescriptorX()[0].Address(), buffer.data(), buffer.size()); + buffer.resize(BufferDescriptorX()[buffer_index].Size()); + Memory::ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), + buffer.size()); } return buffer; } -size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const { - const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()}; - - ASSERT_MSG(size <= GetWriteBufferSize(), "Size %d is too big", size); +size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const { + const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; + const size_t buffer_size{GetWriteBufferSize(buffer_index)}; + if (size > buffer_size) { + NGLOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, + buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } if (is_buffer_b) { - Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size); + Memory::WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); } else { - Memory::WriteBlock(BufferDescriptorC()[0].Address(), buffer, size); + Memory::WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); } return size; } -size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer) const { +size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer, int buffer_index) const { return WriteBuffer(buffer.data(), buffer.size()); } -size_t HLERequestContext::GetReadBufferSize() const { - const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()}; - return is_buffer_a ? BufferDescriptorA()[0].Size() : BufferDescriptorX()[0].Size(); +size_t HLERequestContext::GetReadBufferSize(int buffer_index) const { + const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()}; + return is_buffer_a ? BufferDescriptorA()[buffer_index].Size() + : BufferDescriptorX()[buffer_index].Size(); } -size_t HLERequestContext::GetWriteBufferSize() const { - const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()}; - return is_buffer_b ? BufferDescriptorB()[0].Size() : BufferDescriptorC()[0].Size(); +size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const { + const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; + return is_buffer_b ? BufferDescriptorB()[buffer_index].Size() + : BufferDescriptorC()[buffer_index].Size(); } std::string HLERequestContext::Description() const { diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index b5631b773..376263eac 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -6,6 +6,7 @@ #include <array> #include <memory> +#include <string> #include <vector> #include <boost/container/small_vector.hpp> #include "common/common_types.h" @@ -13,6 +14,7 @@ #include "core/hle/ipc.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/thread.h" namespace Service { class ServiceFrameworkBase; @@ -24,6 +26,7 @@ class Domain; class HandleTable; class HLERequestContext; class Process; +class Event; /** * Interface implemented by HLE Session handlers. @@ -102,14 +105,31 @@ public: return server_session; } + using WakeupCallback = std::function<void(SharedPtr<Thread> thread, HLERequestContext& context, + ThreadWakeupReason reason)>; + + /** + * Puts the specified guest thread to sleep until the returned event is signaled or until the + * specified timeout expires. + * @param thread Thread to be put to sleep. + * @param reason Reason for pausing the thread, to be used for debugging purposes. + * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback + * invoked with a Timeout reason. + * @param callback Callback to be invoked when the thread is resumed. This callback must write + * the entire command response once again, regardless of the state of it before this function + * was called. + * @returns Event that when signaled will resume the thread and call the callback function. + */ + SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason, + u64 timeout, WakeupCallback&& callback); + void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); /// Populates this context with data from the requesting process/thread. ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, HandleTable& src_table); /// Writes data from this context back to the requesting process/thread. - ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, - HandleTable& dst_table); + ResultCode WriteToOutgoingCommandBuffer(Thread& thread); u32_le GetCommand() const { return command; @@ -139,24 +159,24 @@ public: return buffer_c_desciptors; } - const std::unique_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const { + const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const { return domain_message_header; } /// Helper function to read a buffer using the appropriate buffer descriptor - std::vector<u8> ReadBuffer() const; + std::vector<u8> ReadBuffer(int buffer_index = 0) const; /// Helper function to write a buffer using the appropriate buffer descriptor - size_t WriteBuffer(const void* buffer, size_t size) const; + size_t WriteBuffer(const void* buffer, size_t size, int buffer_index = 0) const; /// Helper function to write a buffer using the appropriate buffer descriptor - size_t WriteBuffer(const std::vector<u8>& buffer) const; + size_t WriteBuffer(const std::vector<u8>& buffer, int buffer_index = 0) const; /// Helper function to get the size of the input buffer - size_t GetReadBufferSize() const; + size_t GetReadBufferSize(int buffer_index = 0) const; /// Helper function to get the size of the output buffer - size_t GetWriteBufferSize() const; + size_t GetWriteBufferSize(int buffer_index = 0) const; template <typename T> SharedPtr<T> GetCopyObject(size_t index) { @@ -182,6 +202,16 @@ public: domain_objects.emplace_back(std::move(object)); } + template <typename T> + std::shared_ptr<T> GetDomainRequestHandler(size_t index) const { + return std::static_pointer_cast<T>(domain_request_handlers[index]); + } + + void SetDomainRequestHandlers( + const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) { + domain_request_handlers = handlers; + } + /// Clears the list of objects so that no lingering objects are written accidentally to the /// response buffer. void ClearIncomingObjects() { @@ -212,10 +242,10 @@ private: boost::container::small_vector<SharedPtr<Object>, 8> copy_objects; boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects; - std::unique_ptr<IPC::CommandHeader> command_header; - std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; - std::unique_ptr<IPC::DataPayloadHeader> data_payload_header; - std::unique_ptr<IPC::DomainMessageHeader> domain_message_header; + std::shared_ptr<IPC::CommandHeader> command_header; + std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; + std::shared_ptr<IPC::DataPayloadHeader> data_payload_header; + std::shared_ptr<IPC::DomainMessageHeader> domain_message_header; std::vector<IPC::BufferDescriptorX> buffer_x_desciptors; std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors; std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors; @@ -225,6 +255,8 @@ private: unsigned data_payload_offset{}; unsigned buffer_c_offset{}; u32_le command{}; + + std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; }; } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index b0c3f4ae1..b325b879b 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -41,7 +41,6 @@ void Shutdown() { g_object_address_table.Clear(); Kernel::ThreadingShutdown(); - g_current_process = nullptr; Kernel::TimersShutdown(); Kernel::ResourceLimitsShutdown(); diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index c77e58f3c..402ae900f 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -18,12 +18,10 @@ using Handle = u32; enum class HandleType : u32 { Unknown, Event, - Mutex, SharedMemory, Thread, Process, AddressArbiter, - ConditionVariable, Timer, ResourceLimit, CodeSet, @@ -33,10 +31,6 @@ enum class HandleType : u32 { ServerSession, }; -enum { - DEFAULT_STACK_SIZE = 0x10000, -}; - enum class ResetType { OneShot, Sticky, @@ -67,9 +61,7 @@ public: bool IsWaitable() const { switch (GetHandleType()) { case HandleType::Event: - case HandleType::Mutex: case HandleType::Thread: - case HandleType::ConditionVariable: case HandleType::Timer: case HandleType::ServerPort: case HandleType::ServerSession: diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 0b9dc700c..bc144f3de 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -7,6 +7,7 @@ #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "core/core.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" @@ -15,124 +16,119 @@ namespace Kernel { -void ReleaseThreadMutexes(Thread* thread) { - for (auto& mtx : thread->held_mutexes) { - mtx->SetHasWaiters(false); - mtx->SetHoldingThread(nullptr); - mtx->WakeupAllWaitingThreads(); - } - thread->held_mutexes.clear(); -} +/// Returns the number of threads that are waiting for a mutex, and the highest priority one among +/// those. +static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread( + SharedPtr<Thread> current_thread, VAddr mutex_addr) { -Mutex::Mutex() {} -Mutex::~Mutex() {} + SharedPtr<Thread> highest_priority_thread; + u32 num_waiters = 0; -SharedPtr<Mutex> Mutex::Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr, - std::string name) { - SharedPtr<Mutex> mutex(new Mutex); + for (auto& thread : current_thread->wait_mutex_threads) { + if (thread->mutex_wait_address != mutex_addr) + continue; - mutex->guest_addr = guest_addr; - mutex->name = std::move(name); + ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); - // If mutex was initialized with a holding thread, acquire it by the holding thread - if (holding_thread) { - mutex->Acquire(holding_thread.get()); + ++num_waiters; + if (highest_priority_thread == nullptr || + thread->GetPriority() < highest_priority_thread->GetPriority()) { + highest_priority_thread = thread; + } } - // Mutexes are referenced by guest address, so track this in the kernel - g_object_address_table.Insert(guest_addr, mutex); - - return mutex; + return {highest_priority_thread, num_waiters}; } -bool Mutex::ShouldWait(Thread* thread) const { - auto holding_thread = GetHoldingThread(); - return holding_thread != nullptr && thread != holding_thread; +/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. +static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, + SharedPtr<Thread> new_owner) { + auto threads = current_thread->wait_mutex_threads; + for (auto& thread : threads) { + if (thread->mutex_wait_address != mutex_addr) + continue; + + ASSERT(thread->lock_owner == current_thread); + current_thread->RemoveMutexWaiter(thread); + if (new_owner != thread) + new_owner->AddMutexWaiter(thread); + } } -void Mutex::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); +ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, + Handle requesting_thread_handle) { + // The mutex address must be 4-byte aligned + if ((address % sizeof(u32)) != 0) { + return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); + } - priority = thread->current_priority; - thread->held_mutexes.insert(this); - SetHoldingThread(thread); - thread->UpdatePriority(); - Core::System::GetInstance().PrepareReschedule(); -} + SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle); + SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle); -ResultCode Mutex::Release(Thread* thread) { - auto holding_thread = GetHoldingThread(); - ASSERT(holding_thread); + // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another + // thread. + ASSERT(requesting_thread == GetCurrentThread()); - // We can only release the mutex if it's held by the calling thread. - ASSERT(thread == holding_thread); + u32 addr_value = Memory::Read32(address); + + // If the mutex isn't being held, just return success. + if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) { + return RESULT_SUCCESS; + } + + if (holding_thread == nullptr) + return ERR_INVALID_HANDLE; + + // Wait until the mutex is released + GetCurrentThread()->mutex_wait_address = address; + GetCurrentThread()->wait_handle = requesting_thread_handle; + + GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX; + GetCurrentThread()->wakeup_callback = nullptr; + + // Update the lock holder thread's priority to prevent priority inversion. + holding_thread->AddMutexWaiter(GetCurrentThread()); - holding_thread->held_mutexes.erase(this); - holding_thread->UpdatePriority(); - SetHoldingThread(nullptr); - SetHasWaiters(!GetWaitingThreads().empty()); - WakeupAllWaitingThreads(); Core::System::GetInstance().PrepareReschedule(); return RESULT_SUCCESS; } -void Mutex::AddWaitingThread(SharedPtr<Thread> thread) { - WaitObject::AddWaitingThread(thread); - thread->pending_mutexes.insert(this); - SetHasWaiters(true); - UpdatePriority(); -} - -void Mutex::RemoveWaitingThread(Thread* thread) { - WaitObject::RemoveWaitingThread(thread); - thread->pending_mutexes.erase(this); - if (!GetHasWaiters()) - SetHasWaiters(!GetWaitingThreads().empty()); - UpdatePriority(); -} +ResultCode Mutex::Release(VAddr address) { + // The mutex address must be 4-byte aligned + if ((address % sizeof(u32)) != 0) { + return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); + } -void Mutex::UpdatePriority() { - if (!GetHoldingThread()) - return; + auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); - u32 best_priority = THREADPRIO_LOWEST; - for (auto& waiter : GetWaitingThreads()) { - if (waiter->current_priority < best_priority) - best_priority = waiter->current_priority; + // There are no more threads waiting for the mutex, release it completely. + if (thread == nullptr) { + Memory::Write32(address, 0); + return RESULT_SUCCESS; } - if (best_priority != priority) { - priority = best_priority; - GetHoldingThread()->UpdatePriority(); - } -} + // Transfer the ownership of the mutex from the previous owner to the new one. + TransferMutexOwnership(address, GetCurrentThread(), thread); -Handle Mutex::GetOwnerHandle() const { - GuestState guest_state{Memory::Read32(guest_addr)}; - return guest_state.holding_thread_handle; -} + u32 mutex_value = thread->wait_handle; -SharedPtr<Thread> Mutex::GetHoldingThread() const { - GuestState guest_state{Memory::Read32(guest_addr)}; - return g_handle_table.Get<Thread>(guest_state.holding_thread_handle); -} + if (num_waiters >= 2) { + // Notify the guest that there are still some threads waiting for the mutex + mutex_value |= Mutex::MutexHasWaitersFlag; + } -void Mutex::SetHoldingThread(SharedPtr<Thread> thread) { - GuestState guest_state{Memory::Read32(guest_addr)}; - guest_state.holding_thread_handle.Assign(thread ? thread->guest_handle : 0); - Memory::Write32(guest_addr, guest_state.raw); -} + // Grant the mutex to the next waiting thread and resume it. + Memory::Write32(address, mutex_value); -bool Mutex::GetHasWaiters() const { - GuestState guest_state{Memory::Read32(guest_addr)}; - return guest_state.has_waiters != 0; -} + ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); + thread->ResumeFromWait(); -void Mutex::SetHasWaiters(bool has_waiters) { - GuestState guest_state{Memory::Read32(guest_addr)}; - guest_state.has_waiters.Assign(has_waiters ? 1 : 0); - Memory::Write32(guest_addr, guest_state.raw); -} + thread->lock_owner = nullptr; + thread->condvar_wait_address = 0; + thread->mutex_wait_address = 0; + thread->wait_handle = 0; + return RESULT_SUCCESS; +} } // namespace Kernel diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 38db21005..3117e7c70 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -15,87 +15,23 @@ namespace Kernel { class Thread; -class Mutex final : public WaitObject { +class Mutex final { public: - /** - * Creates a mutex. - * @param holding_thread Specifies a thread already holding the mutex. If not nullptr, this - * thread will acquire the mutex. - * @param guest_addr Address of the object tracking the mutex in guest memory. If specified, - * this mutex will update the guest object when its state changes. - * @param name Optional name of mutex - * @return Pointer to new Mutex object - */ - static SharedPtr<Mutex> Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr = 0, - std::string name = "Unknown"); + /// Flag that indicates that a mutex still has threads waiting for it. + static constexpr u32 MutexHasWaitersFlag = 0x40000000; + /// Mask of the bits in a mutex address value that contain the mutex owner. + static constexpr u32 MutexOwnerMask = 0xBFFFFFFF; - std::string GetTypeName() const override { - return "Mutex"; - } - std::string GetName() const override { - return name; - } + /// Attempts to acquire a mutex at the specified address. + static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle, + Handle requesting_thread_handle); - static const HandleType HANDLE_TYPE = HandleType::Mutex; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - u32 priority; ///< The priority of the mutex, used for priority inheritance. - std::string name; ///< Name of mutex (optional) - VAddr guest_addr; ///< Address of the guest mutex value - - /** - * Elevate the mutex priority to the best priority - * among the priorities of all its waiting threads. - */ - void UpdatePriority(); - - bool ShouldWait(Thread* thread) const override; - void Acquire(Thread* thread) override; - - void AddWaitingThread(SharedPtr<Thread> thread) override; - void RemoveWaitingThread(Thread* thread) override; - - /** - * Attempts to release the mutex from the specified thread. - * @param thread Thread that wants to release the mutex. - * @returns The result code of the operation. - */ - ResultCode Release(Thread* thread); - - /// Gets the handle to the holding process stored in the guest state. - Handle GetOwnerHandle() const; - - /// Gets the Thread pointed to by the owner handle - SharedPtr<Thread> GetHoldingThread() const; - /// Sets the holding process handle in the guest state. - void SetHoldingThread(SharedPtr<Thread> thread); - - /// Returns the has_waiters bit in the guest state. - bool GetHasWaiters() const; - /// Sets the has_waiters bit in the guest state. - void SetHasWaiters(bool has_waiters); + /// Releases the mutex at the specified address. + static ResultCode Release(VAddr address); private: - Mutex(); - ~Mutex() override; - - /// Object in guest memory used to track the mutex state - union GuestState { - u32_le raw; - /// Handle of the thread that currently holds the mutex, 0 if available - BitField<0, 30, u32_le> holding_thread_handle; - /// 1 when there are threads waiting for this mutex, otherwise 0 - BitField<30, 1, u32_le> has_waiters; - }; - static_assert(sizeof(GuestState) == 4, "GuestState size is incorrect"); + Mutex() = default; + ~Mutex() = default; }; -/** - * Releases all the mutexes held by the specified thread - * @param thread Thread that is holding the mutexes - */ -void ReleaseThreadMutexes(Thread* thread); - } // namespace Kernel diff --git a/src/core/hle/kernel/object_address_table.cpp b/src/core/hle/kernel/object_address_table.cpp index 434c16add..ec97f6f8e 100644 --- a/src/core/hle/kernel/object_address_table.cpp +++ b/src/core/hle/kernel/object_address_table.cpp @@ -10,12 +10,12 @@ namespace Kernel { ObjectAddressTable g_object_address_table; void ObjectAddressTable::Insert(VAddr addr, SharedPtr<Object> obj) { - ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%llx", addr); + ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x{:X}", addr); objects[addr] = obj; } void ObjectAddressTable::Close(VAddr addr) { - ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%llx", addr); + ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x{:X}", addr); objects.erase(addr); } diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 8e74059ea..651d932d3 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -20,12 +20,9 @@ namespace Kernel { // Lists all processes that exist in the current session. static std::vector<SharedPtr<Process>> process_list; -SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) { +SharedPtr<CodeSet> CodeSet::Create(std::string name) { SharedPtr<CodeSet> codeset(new CodeSet); - codeset->name = std::move(name); - codeset->program_id = program_id; - return codeset; } @@ -41,6 +38,7 @@ SharedPtr<Process> Process::Create(std::string&& name) { process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); process->status = ProcessStatus::Created; + process->program_id = 0; process_list.push_back(process); return process; @@ -56,7 +54,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { continue; } else if ((type & 0xF00) == 0xE00) { // 0x0FFF // Allowed interrupts list - LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored"); + NGLOG_WARNING(Loader, "ExHeader allowed interrupts list ignored"); } else if ((type & 0xF80) == 0xF00) { // 0x07FF // Allowed syscalls mask unsigned int index = ((descriptor >> 24) & 7) * 24; @@ -76,7 +74,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { } else if ((type & 0xFFE) == 0xFF8) { // 0x001F // Mapped memory range if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) { - LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored."); + NGLOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored."); continue; } u32 end_desc = kernel_caps[i + 1]; @@ -111,19 +109,21 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { int minor = kernel_version & 0xFF; int major = (kernel_version >> 8) & 0xFF; - LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor); + NGLOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor); } else { - LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor); + NGLOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor); } } } void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { - // Allocate and map stack + // Allocate and map the main thread stack + // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part + // of the user address space. vm_manager - .MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, + .MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size, std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, - MemoryState::Heap) + MemoryState::Mapped) .Unwrap(); misc_memory_used += stack_size; memory_region->used += stack_size; @@ -134,7 +134,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { HandleSpecialMapping(vm_manager, mapping); } - vm_manager.LogLayout(Log::Level::Debug); + vm_manager.LogLayout(); status = ProcessStatus::Running; Kernel::SetupMainThread(entry_point, main_thread_priority, this); @@ -155,9 +155,9 @@ void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { }; // Map CodeSet segments - MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::Code); - MapSegment(module_->rodata, VMAPermission::Read, MemoryState::Static); - MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::Static); + MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::CodeStatic); + MapSegment(module_->rodata, VMAPermission::Read, MemoryState::CodeMutable); + MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::CodeMutable); } VAddr Process::GetLinearHeapAreaAddress() const { @@ -184,6 +184,8 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per // Initialize heap heap_memory = std::make_shared<std::vector<u8>>(); heap_start = heap_end = target; + } else { + vm_manager.UnmapRange(heap_start, heap_end - heap_start); } // If necessary, expand backing vector to cover new heap extents. @@ -203,7 +205,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per size, MemoryState::Heap)); vm_manager.Reprotect(vma, perms); - heap_used += size; + heap_used = size; memory_region->used += size; return MakeResult<VAddr>(heap_end - size); @@ -290,7 +292,7 @@ ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { CASCADE_RESULT(auto new_vma, vm_manager.MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, - vma->second.meminfo_state)); + MemoryState::Mapped)); // Protect mirror with permissions from old region vm_manager.Reprotect(new_vma, vma->second.permissions); // Remove permissions from old region @@ -321,5 +323,4 @@ SharedPtr<Process> GetProcessById(u32 process_id) { return *itr; } -SharedPtr<Process> g_current_process; } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index add98472f..68e77a4d1 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -56,7 +56,7 @@ class ResourceLimit; struct MemoryRegionInfo; struct CodeSet final : public Object { - static SharedPtr<CodeSet> Create(std::string name, u64 program_id); + static SharedPtr<CodeSet> Create(std::string name); std::string GetTypeName() const override { return "CodeSet"; @@ -72,8 +72,6 @@ struct CodeSet final : public Object { /// Name of the process std::string name; - /// Title ID corresponding to the process - u64 program_id; std::shared_ptr<std::vector<u8>> memory; @@ -113,6 +111,9 @@ public: static u32 next_process_id; + /// Title ID corresponding to the process + u64 program_id; + /// Resource limit descriptor for this process SharedPtr<ResourceLimit> resource_limit; @@ -202,5 +203,4 @@ void ClearProcessList(); /// Retrieves a process from the current list of processes. SharedPtr<Process> GetProcessById(u32 process_id); -extern SharedPtr<Process> g_current_process; } // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 0149a3ed6..0ef5fc57d 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -29,62 +29,62 @@ SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory cat case ResourceLimitCategory::OTHER: return resource_limits[static_cast<u8>(category)]; default: - LOG_CRITICAL(Kernel, "Unknown resource limit category"); + NGLOG_CRITICAL(Kernel, "Unknown resource limit category"); UNREACHABLE(); } } -s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const { +s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { switch (resource) { - case COMMIT: + case ResourceType::Commit: return current_commit; - case THREAD: + case ResourceType::Thread: return current_threads; - case EVENT: + case ResourceType::Event: return current_events; - case MUTEX: + case ResourceType::Mutex: return current_mutexes; - case SEMAPHORE: + case ResourceType::Semaphore: return current_semaphores; - case TIMER: + case ResourceType::Timer: return current_timers; - case SHARED_MEMORY: + case ResourceType::SharedMemory: return current_shared_mems; - case ADDRESS_ARBITER: + case ResourceType::AddressArbiter: return current_address_arbiters; - case CPU_TIME: + case ResourceType::CPUTime: return current_cpu_time; default: - LOG_ERROR(Kernel, "Unknown resource type=%08X", resource); + NGLOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); UNIMPLEMENTED(); return 0; } } -u32 ResourceLimit::GetMaxResourceValue(u32 resource) const { +u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { switch (resource) { - case PRIORITY: + case ResourceType::Priority: return max_priority; - case COMMIT: + case ResourceType::Commit: return max_commit; - case THREAD: + case ResourceType::Thread: return max_threads; - case EVENT: + case ResourceType::Event: return max_events; - case MUTEX: + case ResourceType::Mutex: return max_mutexes; - case SEMAPHORE: + case ResourceType::Semaphore: return max_semaphores; - case TIMER: + case ResourceType::Timer: return max_timers; - case SHARED_MEMORY: + case ResourceType::SharedMemory: return max_shared_mems; - case ADDRESS_ARBITER: + case ResourceType::AddressArbiter: return max_address_arbiters; - case CPU_TIME: + case ResourceType::CPUTime: return max_cpu_time; default: - LOG_ERROR(Kernel, "Unknown resource type=%08X", resource); + NGLOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); UNIMPLEMENTED(); return 0; } diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 1a0ca11f1..cc689a27a 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h @@ -16,17 +16,17 @@ enum class ResourceLimitCategory : u8 { OTHER = 3 }; -enum ResourceTypes { - PRIORITY = 0, - COMMIT = 1, - THREAD = 2, - EVENT = 3, - MUTEX = 4, - SEMAPHORE = 5, - TIMER = 6, - SHARED_MEMORY = 7, - ADDRESS_ARBITER = 8, - CPU_TIME = 9, +enum class ResourceType { + Priority = 0, + Commit = 1, + Thread = 2, + Event = 3, + Mutex = 4, + Semaphore = 5, + Timer = 6, + SharedMemory = 7, + AddressArbiter = 8, + CPUTime = 9, }; class ResourceLimit final : public Object { @@ -60,14 +60,14 @@ public: * @param resource Requested resource type * @returns The current value of the resource type */ - s32 GetCurrentResourceValue(u32 resource) const; + s32 GetCurrentResourceValue(ResourceType resource) const; /** * Gets the max value for the specified resource. * @param resource Requested resource type * @returns The max value of the resource type */ - u32 GetMaxResourceValue(u32 resource) const; + u32 GetMaxResourceValue(ResourceType resource) const; /// Name of resource limit object. std::string name; diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 235068b22..9cb9e0e5c 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -2,12 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/core_timing.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" namespace Kernel { +std::mutex Scheduler::scheduler_mutex; + Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {} Scheduler::~Scheduler() { @@ -17,6 +20,7 @@ Scheduler::~Scheduler() { } bool Scheduler::HaveReadyThreads() { + std::lock_guard<std::mutex> lock(scheduler_mutex); return ready_queue.get_first() != nullptr; } @@ -67,7 +71,7 @@ void Scheduler::SwitchContext(Thread* new_thread) { // Cancel any outstanding wakeup events for this thread new_thread->CancelWakeupTimer(); - auto previous_process = Kernel::g_current_process; + auto previous_process = Core::CurrentProcess(); current_thread = new_thread; @@ -75,8 +79,8 @@ void Scheduler::SwitchContext(Thread* new_thread) { new_thread->status = THREADSTATUS_RUNNING; if (previous_process != current_thread->owner_process) { - Kernel::g_current_process = current_thread->owner_process; - SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + Core::CurrentProcess() = current_thread->owner_process; + SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); } cpu_core->LoadContext(new_thread->context); @@ -89,41 +93,53 @@ void Scheduler::SwitchContext(Thread* new_thread) { } void Scheduler::Reschedule() { + std::lock_guard<std::mutex> lock(scheduler_mutex); + Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); if (cur && next) { - LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); + NGLOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId()); } else if (cur) { - LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId()); + NGLOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId()); } else if (next) { - LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId()); + NGLOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId()); } SwitchContext(next); } void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) { + std::lock_guard<std::mutex> lock(scheduler_mutex); + thread_list.push_back(thread); ready_queue.prepare(priority); } void Scheduler::RemoveThread(Thread* thread) { + std::lock_guard<std::mutex> lock(scheduler_mutex); + thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), thread_list.end()); } void Scheduler::ScheduleThread(Thread* thread, u32 priority) { + std::lock_guard<std::mutex> lock(scheduler_mutex); + ASSERT(thread->status == THREADSTATUS_READY); ready_queue.push_back(priority, thread); } void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { + std::lock_guard<std::mutex> lock(scheduler_mutex); + ASSERT(thread->status == THREADSTATUS_READY); ready_queue.remove(priority, thread); } void Scheduler::SetThreadPriority(Thread* thread, u32 priority) { + std::lock_guard<std::mutex> lock(scheduler_mutex); + // If thread was ready, adjust queues if (thread->status == THREADSTATUS_READY) ready_queue.move(thread, thread->current_priority, priority); diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index 27d0247d6..a3b5fb8ca 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -4,6 +4,7 @@ #pragma once +#include <mutex> #include <vector> #include "common/common_types.h" #include "common/thread_queue_list.h" @@ -68,6 +69,8 @@ private: SharedPtr<Thread> current_thread = nullptr; ARM_Interface* cpu_core; + + static std::mutex scheduler_mutex; }; } // namespace Kernel diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 5f31cf19b..bf812c543 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -4,6 +4,7 @@ #include <tuple> +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" @@ -60,6 +61,9 @@ void ServerSession::Acquire(Thread* thread) { ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { auto& domain_message_header = context.GetDomainMessageHeader(); if (domain_message_header) { + // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs + context.SetDomainRequestHandlers(domain_request_handlers); + // If there is a DomainMessageHeader, then this is CommandType "Request" const u32 object_id{context.GetDomainMessageHeader()->object_id}; switch (domain_message_header->command) { @@ -67,7 +71,7 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con return domain_request_handlers[object_id - 1]->HandleSyncRequest(context); case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { - LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id); + NGLOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); domain_request_handlers[object_id - 1] = nullptr; @@ -77,7 +81,8 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con } } - LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value()); + NGLOG_CRITICAL(IPC, "Unknown domain command={}", + static_cast<int>(domain_message_header->command.Value())); ASSERT(false); } @@ -91,7 +96,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { Kernel::HLERequestContext context(this); u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress()); - context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, + context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(), Kernel::g_handle_table); ResultCode result = RESULT_SUCCESS; diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index d4505061e..ac4921298 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -4,6 +4,7 @@ #include <cstring> #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/shared_memory.h" @@ -51,8 +52,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u } // Refresh the address mappings for the current process. - if (Kernel::g_current_process != nullptr) { - Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + if (Core::CurrentProcess() != nullptr) { + Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); } } else { auto& vm_manager = shared_memory->owner_process->vm_manager; @@ -106,31 +107,19 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi // Error out if the requested permissions don't match what the creator process allows. if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match", - GetObjectId(), address, name.c_str()); + NGLOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match", + GetObjectId(), address, name); return ERR_INVALID_COMBINATION; } // Error out if the provided permissions are not compatible with what the creator process needs. if (other_permissions != MemoryPermission::DontCare && static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match", - GetObjectId(), address, name.c_str()); + NGLOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match", + GetObjectId(), address, name); return ERR_WRONG_PERMISSION; } - // TODO(Subv): The same process that created a SharedMemory object - // can not map it in its own address space unless it was created with addr=0, result 0xD900182C. - - if (address != 0) { - // TODO(shinyquagsire23): Check for virtual/mappable memory here too? - if (address >= Memory::HEAP_VADDR && address < Memory::HEAP_VADDR_END) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, invalid address", - GetObjectId(), address, name.c_str()); - return ERR_INVALID_ADDRESS; - } - } - VAddr target_address = address; if (base_address == 0 && target_address == 0) { @@ -142,10 +131,10 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi auto result = target_process->vm_manager.MapMemoryBlock( target_address, backing_block, backing_block_offset, size, MemoryState::Shared); if (result.Failed()) { - LOG_ERROR( + NGLOG_ERROR( Kernel, - "cannot map id=%u, target_address=0x%llx name=%s, error mapping to virtual memory", - GetObjectId(), target_address, name.c_str()); + "cannot map id={}, target_address=0x{:X} name={}, error mapping to virtual memory", + GetObjectId(), target_address, name); return result.Code(); } @@ -163,7 +152,7 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { u32 masked_permissions = static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute); return static_cast<VMAPermission>(masked_permissions); -}; +} u8* SharedMemory::GetPointer(u32 offset) { return backing_block->data() + backing_block_offset + offset; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index b2e8b5ce3..097416e9e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -4,14 +4,15 @@ #include <algorithm> #include <cinttypes> +#include <iterator> #include "common/logging/log.h" #include "common/microprofile.h" #include "common/string_util.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/condition_variable.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/mutex.h" @@ -30,30 +31,30 @@ namespace Kernel { /// Set the process heap to a given Size. It can both extend and shrink the heap. static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) { - LOG_TRACE(Kernel_SVC, "called, heap_size=0x%llx", heap_size); - auto& process = *g_current_process; + NGLOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size); + auto& process = *Core::CurrentProcess(); CASCADE_RESULT(*heap_addr, process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite)); return RESULT_SUCCESS; } static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%llx", addr); + NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x{:X}", addr); return RESULT_SUCCESS; } /// Maps a memory range into a different range. static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr, - src_addr, size); - return g_current_process->MirrorMemory(dst_addr, src_addr, size); + NGLOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size); } /// Unmaps a region that was previously mapped with svcMapMemory static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr, - src_addr, size); - return g_current_process->UnmapMemory(dst_addr, src_addr, size); + NGLOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -67,11 +68,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address if (port_name.size() > PortNameMaxLength) return ERR_PORT_NAME_TOO_LONG; - LOG_TRACE(Kernel_SVC, "called port_name=%s", port_name.c_str()); + NGLOG_TRACE(Kernel_SVC, "called port_name={}", port_name); auto it = Service::g_kernel_named_ports.find(port_name); if (it == Service::g_kernel_named_ports.end()) { - LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: %s", port_name.c_str()); + NGLOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name); return ERR_NOT_FOUND; } @@ -89,11 +90,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address static ResultCode SendSyncRequest(Handle handle) { SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle); if (!session) { - LOG_ERROR(Kernel_SVC, "called with invalid handle=0x%08X", handle); + NGLOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle); return ERR_INVALID_HANDLE; } - LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); + NGLOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); Core::System::GetInstance().PrepareReschedule(); @@ -104,7 +105,7 @@ static ResultCode SendSyncRequest(Handle handle) { /// Get the ID for the specified thread. static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { - LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle); + NGLOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); if (!thread) { @@ -117,7 +118,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { /// Get the ID of the specified process static ResultCode GetProcessId(u32* process_id, Handle process_handle) { - LOG_TRACE(Kernel_SVC, "called process=0x%08X", process_handle); + NGLOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); const SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle); if (!process) { @@ -144,41 +145,11 @@ static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thr return true; }; -/// Wait for a kernel object to synchronize, timeout after the specified nanoseconds -static ResultCode WaitSynchronization1( - SharedPtr<WaitObject> object, Thread* thread, s64 nano_seconds = -1, - std::function<Thread::WakeupCallback> wakeup_callback = DefaultThreadWakeupCallback) { - - if (!object) { - return ERR_INVALID_HANDLE; - } - - if (object->ShouldWait(thread)) { - if (nano_seconds == 0) { - return RESULT_TIMEOUT; - } - - thread->wait_objects = {object}; - object->AddWaitingThread(thread); - thread->status = THREADSTATUS_WAIT_SYNCH_ANY; - - // Create an event to wake the thread up after the specified nanosecond delay has passed - thread->WakeAfterDelay(nano_seconds); - thread->wakeup_callback = wakeup_callback; - - Core::System::GetInstance().PrepareReschedule(); - } else { - object->Acquire(thread); - } - - return RESULT_SUCCESS; -} - /// Wait for the given handles to synchronize, timeout after the specified nanoseconds static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 handle_count, s64 nano_seconds) { - LOG_TRACE(Kernel_SVC, "called handles_address=0x%llx, handle_count=%d, nano_seconds=%d", - handles_address, handle_count, nano_seconds); + NGLOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}", + handles_address, handle_count, nano_seconds); if (!Memory::IsValidVirtualAddress(handles_address)) return ERR_INVALID_POINTER; @@ -231,14 +202,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 thread->WakeAfterDelay(nano_seconds); thread->wakeup_callback = DefaultThreadWakeupCallback; - Core::System::GetInstance().PrepareReschedule(); + Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); return RESULT_TIMEOUT; } /// Resumes a thread waiting on WaitSynchronization static ResultCode CancelSynchronization(Handle thread_handle) { - LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle); + NGLOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle); const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); if (!thread) { @@ -255,74 +226,56 @@ static ResultCode CancelSynchronization(Handle thread_handle) { /// Attempts to locks a mutex, creating it if it does not already exist static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, Handle requesting_thread_handle) { - LOG_TRACE(Kernel_SVC, - "called holding_thread_handle=0x%08X, mutex_addr=0x%llx, " - "requesting_current_thread_handle=0x%08X", - holding_thread_handle, mutex_addr, requesting_thread_handle); - - SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle); - SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle); - - ASSERT(requesting_thread); - ASSERT(requesting_thread == GetCurrentThread()); - - SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr); - if (!mutex) { - // Create a new mutex for the specified address if one does not already exist - mutex = Mutex::Create(holding_thread, mutex_addr); - mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr); - } - - ASSERT(holding_thread == mutex->GetHoldingThread()); + NGLOG_TRACE(Kernel_SVC, + "called holding_thread_handle=0x{:08X}, mutex_addr=0x{:X}, " + "requesting_current_thread_handle=0x{:08X}", + holding_thread_handle, mutex_addr, requesting_thread_handle); - return WaitSynchronization1(mutex, requesting_thread.get()); + return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle); } /// Unlock a mutex static ResultCode ArbitrateUnlock(VAddr mutex_addr) { - LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr); - - SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr); - ASSERT(mutex); + NGLOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr); - return mutex->Release(GetCurrentThread()); + return Mutex::Release(mutex_addr); } /// Break program execution static void Break(u64 unk_0, u64 unk_1, u64 unk_2) { - LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!"); + NGLOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!"); ASSERT(false); } /// Used to output a message on a debug hardware unit - does nothing on a retail unit static void OutputDebugString(VAddr address, s32 len) { - std::vector<char> string(len); - Memory::ReadBlock(address, string.data(), len); - LOG_DEBUG(Debug_Emulated, "%.*s", len, string.data()); + std::string str(len, '\0'); + Memory::ReadBlock(address, str.data(), str.size()); + NGLOG_DEBUG(Debug_Emulated, "{}", str); } /// Gets system/memory information for the current process static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) { - LOG_TRACE(Kernel_SVC, "called info_id=0x%X, info_sub_id=0x%X, handle=0x%08X", info_id, - info_sub_id, handle); + NGLOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, + info_sub_id, handle); - auto& vm_manager = g_current_process->vm_manager; + auto& vm_manager = Core::CurrentProcess()->vm_manager; switch (static_cast<GetInfoType>(info_id)) { case GetInfoType::AllowedCpuIdBitmask: - *result = g_current_process->allowed_processor_mask; + *result = Core::CurrentProcess()->allowed_processor_mask; break; case GetInfoType::AllowedThreadPrioBitmask: - *result = g_current_process->allowed_thread_priority_mask; + *result = Core::CurrentProcess()->allowed_thread_priority_mask; break; case GetInfoType::MapRegionBaseAddr: - *result = vm_manager.GetMapRegionBaseAddr(); + *result = Memory::MAP_REGION_VADDR; break; case GetInfoType::MapRegionSize: - *result = vm_manager.GetAddressSpaceSize(); + *result = Memory::MAP_REGION_SIZE; break; case GetInfoType::HeapRegionBaseAddr: - *result = vm_manager.GetNewMapRegionBaseAddr() + vm_manager.GetNewMapRegionSize(); + *result = Memory::HEAP_VADDR; break; case GetInfoType::HeapRegionSize: *result = Memory::HEAP_SIZE; @@ -346,21 +299,21 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) *result = vm_manager.GetAddressSpaceSize(); break; case GetInfoType::NewMapRegionBaseAddr: - *result = vm_manager.GetNewMapRegionBaseAddr(); + *result = Memory::NEW_MAP_REGION_VADDR; break; case GetInfoType::NewMapRegionSize: - *result = vm_manager.GetNewMapRegionSize(); + *result = Memory::NEW_MAP_REGION_SIZE; break; case GetInfoType::IsVirtualAddressMemoryEnabled: - *result = g_current_process->is_virtual_address_memory_enabled; + *result = Core::CurrentProcess()->is_virtual_address_memory_enabled; break; case GetInfoType::TitleId: - LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0"); + NGLOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0"); *result = 0; break; case GetInfoType::PrivilegedProcessId: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query priviledged process id bounds, returned 0"); + NGLOG_WARNING(Kernel_SVC, + "(STUBBED) Attempted to query privileged process id bounds, returned 0"); *result = 0; break; case GetInfoType::UserExceptionContextAddr: @@ -375,6 +328,19 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) return RESULT_SUCCESS; } +/// Sets the thread activity +static ResultCode SetThreadActivity(Handle handle, u32 unknown) { + NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, unknown=0x{:08X}", handle, + unknown); + return RESULT_SUCCESS; +} + +/// Gets the thread context +static ResultCode GetThreadContext(Handle handle, VAddr addr) { + NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, addr=0x{:X}", handle, addr); + return RESULT_SUCCESS; +} + /// Gets the priority for the specified thread static ResultCode GetThreadPriority(u32* priority, Handle handle) { const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle); @@ -397,33 +363,29 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { // Note: The kernel uses the current process's resource limit instead of // the one from the thread owner's resource limit. - SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; - if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { + SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit; + if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) { return ERR_NOT_AUTHORIZED; } thread->SetPriority(priority); - thread->UpdatePriority(); - // Update the mutexes that this thread is waiting for - for (auto& mutex : thread->pending_mutexes) - mutex->UpdatePriority(); - - Core::System::GetInstance().PrepareReschedule(); + Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); return RESULT_SUCCESS; } /// Get which CPU core is executing the current thread static u32 GetCurrentProcessorNumber() { - LOG_WARNING(Kernel_SVC, "(STUBBED) called, defaulting to processor 0"); - return 0; + NGLOG_TRACE(Kernel_SVC, "called"); + return GetCurrentThread()->processor_id; } static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size, u32 permissions) { - LOG_TRACE(Kernel_SVC, - "called, shared_memory_handle=0x%08X, addr=0x%llx, size=0x%llx, permissions=0x%08X", - shared_memory_handle, addr, size, permissions); + NGLOG_TRACE( + Kernel_SVC, + "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + shared_memory_handle, addr, size, permissions); SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { @@ -440,23 +402,22 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s case MemoryPermission::WriteExecute: case MemoryPermission::ReadWriteExecute: case MemoryPermission::DontCare: - return shared_memory->Map(g_current_process.get(), addr, permissions_type, + return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, MemoryPermission::DontCare); default: - LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions); + NGLOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions); } return RESULT_SUCCESS; } static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { - LOG_WARNING(Kernel_SVC, - "called, shared_memory_handle=0x%08X, addr=0x%" PRIx64 ", size=0x%" PRIx64 "", - shared_memory_handle, addr, size); + NGLOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}", + shared_memory_handle, addr, size); SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle); - return shared_memory->Unmap(g_current_process.get(), addr); + return shared_memory->Unmap(Core::CurrentProcess().get(), addr); } /// Query process memory @@ -468,11 +429,11 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i } auto vma = process->vm_manager.FindVMA(addr); memory_info->attributes = 0; - if (vma == g_current_process->vm_manager.vma_map.end()) { + if (vma == Core::CurrentProcess()->vm_manager.vma_map.end()) { memory_info->base_address = 0; memory_info->permission = static_cast<u32>(VMAPermission::None); memory_info->size = 0; - memory_info->type = static_cast<u32>(MemoryState::Free); + memory_info->type = static_cast<u32>(MemoryState::Unmapped); } else { memory_info->base_address = vma->second.base; memory_info->permission = static_cast<u32>(vma->second.permissions); @@ -480,40 +441,47 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i memory_info->type = static_cast<u32>(vma->second.meminfo_state); } - LOG_TRACE(Kernel_SVC, "called process=0x%08X addr=%llx", process_handle, addr); + NGLOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr); return RESULT_SUCCESS; } /// Query memory static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) { - LOG_TRACE(Kernel_SVC, "called, addr=%llx", addr); + NGLOG_TRACE(Kernel_SVC, "called, addr={:X}", addr); return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr); } /// Exits the current process static void ExitProcess() { - LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id); + NGLOG_INFO(Kernel_SVC, "Process {} exiting", Core::CurrentProcess()->process_id); - ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited"); + ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running, + "Process has already exited"); - g_current_process->status = ProcessStatus::Exited; + Core::CurrentProcess()->status = ProcessStatus::Exited; - // Stop all the process threads that are currently waiting for objects. - auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList(); - for (auto& thread : thread_list) { - if (thread->owner_process != g_current_process) - continue; + auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) { + for (auto& thread : thread_list) { + if (thread->owner_process != Core::CurrentProcess()) + continue; - if (thread == GetCurrentThread()) - continue; + if (thread == GetCurrentThread()) + continue; - // TODO(Subv): When are the other running/ready threads terminated? - ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL, - "Exiting processes with non-waiting threads is currently unimplemented"); + // TODO(Subv): When are the other running/ready threads terminated? + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Exiting processes with non-waiting threads is currently unimplemented"); - thread->Stop(); - } + thread->Stop(); + } + }; + + auto& system = Core::System::GetInstance(); + stop_threads(system.Scheduler(0)->GetThreadList()); + stop_threads(system.Scheduler(1)->GetThreadList()); + stop_threads(system.Scheduler(2)->GetThreadList()); + stop_threads(system.Scheduler(3)->GetThreadList()); // Kill the current thread GetCurrentThread()->Stop(); @@ -524,72 +492,71 @@ static void ExitProcess() { /// Creates a new thread static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top, u32 priority, s32 processor_id) { - std::string name = Common::StringFromFormat("unknown-%llx", entry_point); + std::string name = fmt::format("unknown-{:X}", entry_point); if (priority > THREADPRIO_LOWEST) { return ERR_OUT_OF_RANGE; } - SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; - if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { + SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit; + if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) { return ERR_NOT_AUTHORIZED; } if (processor_id == THREADPROCESSORID_DEFAULT) { // Set the target CPU to the one specified in the process' exheader. - processor_id = g_current_process->ideal_processor; + processor_id = Core::CurrentProcess()->ideal_processor; ASSERT(processor_id != THREADPROCESSORID_DEFAULT); } switch (processor_id) { case THREADPROCESSORID_0: - break; case THREADPROCESSORID_1: case THREADPROCESSORID_2: case THREADPROCESSORID_3: - // TODO(bunnei): Implement support for other processor IDs - LOG_ERROR(Kernel_SVC, - "Newly created thread must run in another thread (%u), unimplemented.", - processor_id); break; default: - ASSERT_MSG(false, "Unsupported thread processor ID: %d", processor_id); + ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); break; } CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(name, entry_point, priority, arg, processor_id, stack_top, - g_current_process)); + Core::CurrentProcess())); CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread)); *out_handle = thread->guest_handle; Core::System::GetInstance().PrepareReschedule(); + Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); - LOG_TRACE(Kernel_SVC, - "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " - "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", - entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle); + NGLOG_TRACE(Kernel_SVC, + "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, " + "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", + entry_point, name, arg, stack_top, priority, processor_id, *out_handle); return RESULT_SUCCESS; } /// Starts the thread for the provided handle static ResultCode StartThread(Handle thread_handle) { - LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle); + NGLOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } + ASSERT(thread->status == THREADSTATUS_DORMANT); + thread->ResumeFromWait(); + Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); return RESULT_SUCCESS; } /// Called when a thread exits static void ExitThread() { - LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC()); + NGLOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC()); ExitCurrentThread(); Core::System::GetInstance().PrepareReschedule(); @@ -597,11 +564,11 @@ static void ExitThread() { /// Sleep the current thread static void SleepThread(s64 nanoseconds) { - LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); + NGLOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); // Don't attempt to yield execution if there are no available threads to run, // this way we avoid a useless reschedule to the idle thread. - if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads()) + if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) return; // Sleep current thread and check for next thread to schedule @@ -616,111 +583,107 @@ static void SleepThread(s64 nanoseconds) { /// Signal process wide key atomic static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr, Handle thread_handle, s64 nano_seconds) { - LOG_TRACE( + NGLOG_TRACE( Kernel_SVC, - "called mutex_addr=%llx, condition_variable_addr=%llx, thread_handle=0x%08X, timeout=%d", + "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}", mutex_addr, condition_variable_addr, thread_handle, nano_seconds); SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); ASSERT(thread); - SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr); - if (!mutex) { - // Create a new mutex for the specified address if one does not already exist - mutex = Mutex::Create(thread, mutex_addr); - mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr); - } + CASCADE_CODE(Mutex::Release(mutex_addr)); - SharedPtr<ConditionVariable> condition_variable = - g_object_address_table.Get<ConditionVariable>(condition_variable_addr); - if (!condition_variable) { - // Create a new condition_variable for the specified address if one does not already exist - condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap(); - condition_variable->name = - Common::StringFromFormat("condition-variable-%llx", condition_variable_addr); - } + SharedPtr<Thread> current_thread = GetCurrentThread(); + current_thread->condvar_wait_address = condition_variable_addr; + current_thread->mutex_wait_address = mutex_addr; + current_thread->wait_handle = thread_handle; + current_thread->status = THREADSTATUS_WAIT_MUTEX; + current_thread->wakeup_callback = nullptr; - if (condition_variable->mutex_addr) { - // Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify - // everything is correct - ASSERT(condition_variable->mutex_addr == mutex_addr); - } else { - // Previously created the ConditionVariable using SignalProcessWideKey, set the mutex - // associated with it - condition_variable->mutex_addr = mutex_addr; - } + current_thread->WakeAfterDelay(nano_seconds); - if (mutex->GetOwnerHandle()) { - // Release the mutex if the current thread is holding it - mutex->Release(thread.get()); - } + // Note: Deliberately don't attempt to inherit the lock owner's priority. - auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason, - SharedPtr<Thread> thread, - SharedPtr<WaitObject> object, size_t index) { - ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY); - - if (reason == ThreadWakeupReason::Timeout) { - thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); - return true; - } - - ASSERT(reason == ThreadWakeupReason::Signal); + Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); + return RESULT_SUCCESS; +} - // Now try to acquire the mutex and don't resume if it's not available. - if (!mutex->ShouldWait(thread.get())) { - mutex->Acquire(thread.get()); - thread->SetWaitSynchronizationResult(RESULT_SUCCESS); - return true; - } +/// Signal process wide key +static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) { + NGLOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", + condition_variable_addr, target); + + auto RetrieveWaitingThreads = + [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr condvar_addr) { + const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); + auto& thread_list = scheduler->GetThreadList(); + + for (auto& thread : thread_list) { + if (thread->condvar_wait_address == condvar_addr) + waiting_threads.push_back(thread); + } + }; + + // Retrieve a list of all threads that are waiting for this condition variable. + std::vector<SharedPtr<Thread>> waiting_threads; + RetrieveWaitingThreads(0, waiting_threads, condition_variable_addr); + RetrieveWaitingThreads(1, waiting_threads, condition_variable_addr); + RetrieveWaitingThreads(2, waiting_threads, condition_variable_addr); + RetrieveWaitingThreads(3, waiting_threads, condition_variable_addr); + // Sort them by priority, such that the highest priority ones come first. + std::sort(waiting_threads.begin(), waiting_threads.end(), + [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { + return lhs->current_priority < rhs->current_priority; + }); + + // Only process up to 'target' threads, unless 'target' is -1, in which case process + // them all. + size_t last = waiting_threads.size(); + if (target != -1) + last = target; + + // If there are no threads waiting on this condition variable, just exit + if (last > waiting_threads.size()) + return RESULT_SUCCESS; - if (nano_seconds == 0) { - thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); - return true; - } + for (size_t index = 0; index < last; ++index) { + auto& thread = waiting_threads[index]; - thread->wait_objects = {mutex}; - mutex->AddWaitingThread(thread); - thread->status = THREADSTATUS_WAIT_SYNCH_ANY; + ASSERT(thread->condvar_wait_address == condition_variable_addr); - // Create an event to wake the thread up after the - // specified nanosecond delay has passed - thread->WakeAfterDelay(nano_seconds); - thread->wakeup_callback = DefaultThreadWakeupCallback; + // If the mutex is not yet acquired, acquire it. + u32 mutex_val = Memory::Read32(thread->mutex_wait_address); - Core::System::GetInstance().PrepareReschedule(); + if (mutex_val == 0) { + // We were able to acquire the mutex, resume this thread. + Memory::Write32(thread->mutex_wait_address, thread->wait_handle); + ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); + thread->ResumeFromWait(); - return false; - }; - CASCADE_CODE( - WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback)); + auto lock_owner = thread->lock_owner; + if (lock_owner) + lock_owner->RemoveMutexWaiter(thread); - return RESULT_SUCCESS; -} + thread->lock_owner = nullptr; + thread->mutex_wait_address = 0; + thread->condvar_wait_address = 0; + thread->wait_handle = 0; + } else { + // Couldn't acquire the mutex, block the thread. + Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); + auto owner = g_handle_table.Get<Thread>(owner_handle); + ASSERT(owner); + ASSERT(thread->status != THREADSTATUS_RUNNING); + thread->status = THREADSTATUS_WAIT_MUTEX; + thread->wakeup_callback = nullptr; -/// Signal process wide key -static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) { - LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x", - condition_variable_addr, target); - - // Wakeup all or one thread - Any other value is unimplemented - ASSERT(target == -1 || target == 1); - - SharedPtr<ConditionVariable> condition_variable = - g_object_address_table.Get<ConditionVariable>(condition_variable_addr); - if (!condition_variable) { - // Create a new condition_variable for the specified address if one does not already exist - condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap(); - condition_variable->name = - Common::StringFromFormat("condition-variable-%llx", condition_variable_addr); - } + // Signal that the mutex now has a waiting thread. + Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); - CASCADE_CODE(condition_variable->Release(target)); + owner->AddMutexWaiter(thread); - if (condition_variable->mutex_addr) { - // If a mutex was created for this condition_variable, wait the current thread on it - SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(condition_variable->mutex_addr); - return WaitSynchronization1(mutex, GetCurrentThread()); + Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); + } } return RESULT_SUCCESS; @@ -738,13 +701,13 @@ static u64 GetSystemTick() { /// Close a handle static ResultCode CloseHandle(Handle handle) { - LOG_TRACE(Kernel_SVC, "Closing handle 0x%08X", handle); + NGLOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); return g_handle_table.Close(handle); } /// Reset an event static ResultCode ResetSignal(Handle handle) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x%08X", handle); + NGLOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle); auto event = g_handle_table.Get<Event>(handle); ASSERT(event != nullptr); event->Clear(); @@ -753,21 +716,69 @@ static ResultCode ResetSignal(Handle handle) { /// Creates a TransferMemory object static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%llx, size=0x%llx, perms=%08X", addr, size, - permissions); + NGLOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, + size, permissions); *handle = 0; return RESULT_SUCCESS; } -static ResultCode SetThreadCoreMask(u64, u64, u64) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called"); +static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) { + NGLOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); + + const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); + if (!thread) { + return ERR_INVALID_HANDLE; + } + + *core = thread->ideal_core; + *mask = thread->affinity_mask; + + return RESULT_SUCCESS; +} + +static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { + NGLOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle, + mask, core); + + const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); + if (!thread) { + return ERR_INVALID_HANDLE; + } + + if (core == THREADPROCESSORID_DEFAULT) { + ASSERT(thread->owner_process->ideal_processor != THREADPROCESSORID_DEFAULT); + // Set the target CPU to the one specified in the process' exheader. + core = thread->owner_process->ideal_processor; + mask = 1 << core; + } + + if (mask == 0) { + return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination); + } + + /// This value is used to only change the affinity mask without changing the current ideal core. + static constexpr u32 OnlyChangeMask = static_cast<u32>(-3); + + if (core == OnlyChangeMask) { + core = thread->ideal_core; + } else if (core >= Core::NUM_CPU_CORES && core != -1) { + return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); + } + + // Error out if the input core isn't enabled in the input mask. + if (core < Core::NUM_CPU_CORES && (mask & (1 << core)) == 0) { + return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination); + } + + thread->ChangeCore(core, mask); + return RESULT_SUCCESS; } static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions, u32 remote_permissions) { - LOG_TRACE(Kernel_SVC, "called, size=0x%llx, localPerms=0x%08x, remotePerms=0x%08x", size, - local_permissions, remote_permissions); + NGLOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size, + local_permissions, remote_permissions); auto sharedMemHandle = SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size, static_cast<MemoryPermission>(local_permissions), @@ -778,7 +789,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss } static ResultCode ClearEvent(Handle handle) { - LOG_TRACE(Kernel_SVC, "called, event=0xX", handle); + NGLOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle); SharedPtr<Event> evt = g_handle_table.Get<Event>(handle); if (evt == nullptr) @@ -812,7 +823,7 @@ static const FunctionDef SVC_Table[] = { {0x0B, SvcWrap<SleepThread>, "SleepThread"}, {0x0C, SvcWrap<GetThreadPriority>, "GetThreadPriority"}, {0x0D, SvcWrap<SetThreadPriority>, "SetThreadPriority"}, - {0x0E, nullptr, "GetThreadCoreMask"}, + {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"}, {0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"}, {0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"}, {0x11, nullptr, "SignalEvent"}, @@ -844,14 +855,14 @@ static const FunctionDef SVC_Table[] = { {0x2B, nullptr, "FlushDataCache"}, {0x2C, nullptr, "MapPhysicalMemory"}, {0x2D, nullptr, "UnmapPhysicalMemory"}, - {0x2E, nullptr, "Unknown"}, + {0x2E, nullptr, "GetNextThreadInfo"}, {0x2F, nullptr, "GetLastThreadInfo"}, {0x30, nullptr, "GetResourceLimitLimitValue"}, {0x31, nullptr, "GetResourceLimitCurrentValue"}, - {0x32, nullptr, "SetThreadActivity"}, - {0x33, nullptr, "GetThreadContext"}, - {0x34, nullptr, "Unknown"}, - {0x35, nullptr, "Unknown"}, + {0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"}, + {0x33, SvcWrap<GetThreadContext>, "GetThreadContext"}, + {0x34, nullptr, "WaitForAddress"}, + {0x35, nullptr, "SignalToAddress"}, {0x36, nullptr, "Unknown"}, {0x37, nullptr, "Unknown"}, {0x38, nullptr, "Unknown"}, @@ -859,7 +870,7 @@ static const FunctionDef SVC_Table[] = { {0x3A, nullptr, "Unknown"}, {0x3B, nullptr, "Unknown"}, {0x3C, nullptr, "DumpInfo"}, - {0x3D, nullptr, "Unknown"}, + {0x3D, nullptr, "DumpInfoNew"}, {0x3E, nullptr, "Unknown"}, {0x3F, nullptr, "Unknown"}, {0x40, nullptr, "CreateSession"}, @@ -870,9 +881,9 @@ static const FunctionDef SVC_Table[] = { {0x45, nullptr, "CreateEvent"}, {0x46, nullptr, "Unknown"}, {0x47, nullptr, "Unknown"}, - {0x48, nullptr, "Unknown"}, - {0x49, nullptr, "Unknown"}, - {0x4A, nullptr, "Unknown"}, + {0x48, nullptr, "AllocateUnsafeMemory"}, + {0x49, nullptr, "FreeUnsafeMemory"}, + {0x4A, nullptr, "SetUnsafeAllocationLimit"}, {0x4B, nullptr, "CreateJitMemory"}, {0x4C, nullptr, "MapJitMemory"}, {0x4D, nullptr, "SleepSystem"}, @@ -909,7 +920,7 @@ static const FunctionDef SVC_Table[] = { {0x6C, nullptr, "SetHardwareBreakPoint"}, {0x6D, nullptr, "GetDebugThreadParam"}, {0x6E, nullptr, "Unknown"}, - {0x6F, nullptr, "Unknown"}, + {0x6F, nullptr, "GetMemoryInfo"}, {0x70, nullptr, "CreatePort"}, {0x71, nullptr, "ManageNamedPort"}, {0x72, nullptr, "ConnectToPort"}, @@ -929,8 +940,8 @@ static const FunctionDef SVC_Table[] = { }; static const FunctionDef* GetSVCInfo(u32 func_num) { - if (func_num >= ARRAY_SIZE(SVC_Table)) { - LOG_ERROR(Kernel_SVC, "unknown svc=0x%02X", func_num); + if (func_num >= std::size(SVC_Table)) { + NGLOG_ERROR(Kernel_SVC, "Unknown svc=0x{:02X}", func_num); return nullptr; } return &SVC_Table[func_num]; @@ -949,10 +960,10 @@ void CallSVC(u32 immediate) { if (info->func) { info->func(); } else { - LOG_CRITICAL(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); + NGLOG_CRITICAL(Kernel_SVC, "Unimplemented SVC function {}(..)", info->name); } } else { - LOG_CRITICAL(Kernel_SVC, "unknown SVC function 0x%x", immediate); + NGLOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate); } } diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index bc471d01e..70148c4fe 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -47,9 +47,12 @@ enum class GetInfoType : u64 { NewMapRegionSize = 15, // 3.0.0+ IsVirtualAddressMemoryEnabled = 16, + PersonalMmHeapUsage = 17, TitleId = 18, // 4.0.0+ PrivilegedProcessId = 19, + // 5.0.0+ + UserExceptionContextAddr = 20, }; void CallSVC(u32 immediate); diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index b224f5e67..40aa88cc1 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -13,14 +13,14 @@ namespace Kernel { -#define PARAM(n) Core::CPU().GetReg(n) +#define PARAM(n) Core::CurrentArmInterface().GetReg(n) /** * HLE a function return from the current ARM userland process * @param res Result to return */ static inline void FuncReturn(u64 res) { - Core::CPU().SetReg(0, res); + Core::CurrentArmInterface().SetReg(0, res); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -45,7 +45,7 @@ template <ResultCode func(u32*, u32)> void SvcWrap() { u32 param_1 = 0; u32 retval = func(¶m_1, (u32)PARAM(1)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -53,7 +53,7 @@ template <ResultCode func(u32*, u64)> void SvcWrap() { u32 param_1 = 0; u32 retval = func(¶m_1, PARAM(1)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -66,10 +66,30 @@ template <ResultCode func(u64*, u64)> void SvcWrap() { u64 param_1 = 0; u32 retval = func(¶m_1, PARAM(1)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } +template <ResultCode func(u32, u64)> +void SvcWrap() { + FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), PARAM(1)).raw); +} + +template <ResultCode func(u32, u32, u64)> +void SvcWrap() { + FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw); +} + +template <ResultCode func(u32, u32*, u64*)> +void SvcWrap() { + u32 param_1 = 0; + u64 param_2 = 0; + ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); + Core::CurrentArmInterface().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(2, param_2); + FuncReturn(retval.raw); +} + template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( @@ -100,7 +120,7 @@ template <ResultCode func(u32*, u64, u64, s64)> void SvcWrap() { u32 param_1 = 0; ResultCode retval = func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3)); - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval.raw); } @@ -113,7 +133,7 @@ template <ResultCode func(u64*, u64, u64, u64)> void SvcWrap() { u64 param_1 = 0; u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -123,7 +143,7 @@ void SvcWrap() { u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF)) .raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -146,7 +166,7 @@ template <ResultCode func(u32*, u64, u64, u32)> void SvcWrap() { u32 param_1 = 0; u32 retval = func(¶m_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -155,7 +175,7 @@ void SvcWrap() { u32 param_1 = 0; u32 retval = func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; - Core::CPU().SetReg(1, param_1); + Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index dd0a8ae48..cffa7ca83 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -55,16 +55,6 @@ inline static u32 const NewThreadId() { Thread::Thread() {} Thread::~Thread() {} -/** - * Check if the specified thread is waiting on the specified address to be arbitrated - * @param thread The thread to test - * @param wait_address The address to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { - return thread->status == THREADSTATUS_WAIT_ARB && wait_address == thread->wait_address; -} - void Thread::Stop() { // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); @@ -74,7 +64,7 @@ void Thread::Stop() { // Clean up thread from ready queue // This is only needed when the thread is termintated forcefully (SVC TerminateProcess) if (status == THREADSTATUS_READY) { - Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority); + scheduler->UnscheduleThread(this, current_priority); } status = THREADSTATUS_DEAD; @@ -87,14 +77,11 @@ void Thread::Stop() { } wait_objects.clear(); - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - // Mark the TLS slot in the thread's page as free. u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u64 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; - Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); + Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot); } void WaitCurrentThread_Sleep() { @@ -102,16 +89,10 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { - Thread* thread = GetCurrentThread(); - thread->wait_address = wait_address; - thread->status = THREADSTATUS_WAIT_ARB; -} - void ExitCurrentThread() { Thread* thread = GetCurrentThread(); thread->Stop(); - Core::System::GetInstance().Scheduler().RemoveThread(thread); + Core::System::GetInstance().CurrentScheduler().RemoveThread(thread); } /** @@ -120,16 +101,18 @@ void ExitCurrentThread() { * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time */ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { - SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>((Handle)thread_handle); + const auto proper_handle = static_cast<Handle>(thread_handle); + SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle); if (thread == nullptr) { - LOG_CRITICAL(Kernel, "Callback fired for invalid thread %08X", (Handle)thread_handle); + NGLOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle); return; } bool resume = true; if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || + thread->status == THREADSTATUS_WAIT_HLE_EVENT) { // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -141,6 +124,22 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0); } + if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 || + thread->wait_handle) { + ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); + thread->mutex_wait_address = 0; + thread->condvar_wait_address = 0; + thread->wait_handle = 0; + + auto lock_owner = thread->lock_owner; + // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance + // and don't have a lock owner unless SignalProcessWideKey was called first and the thread + // wasn't awakened due to the mutex already being acquired. + if (lock_owner) { + lock_owner->RemoveMutexWaiter(thread); + } + } + if (resume) thread->ResumeFromWait(); } @@ -150,22 +149,36 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { if (nanoseconds == -1) return; - CoreTiming::ScheduleEvent(nsToCycles(nanoseconds), ThreadWakeupEventType, callback_handle); + CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType, + callback_handle); } void Thread::CancelWakeupTimer() { CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); } +static boost::optional<s32> GetNextProcessorId(u64 mask) { + for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) { + if (mask & (1ULL << index)) { + if (!Core::System().GetInstance().Scheduler(index)->GetCurrentThread()) { + // Core is enabled and not running any threads, use this one + return index; + } + } + } + return {}; +} + void Thread::ResumeFromWait() { ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); switch (status) { case THREADSTATUS_WAIT_SYNCH_ALL: case THREADSTATUS_WAIT_SYNCH_ANY: - case THREADSTATUS_WAIT_ARB: + case THREADSTATUS_WAIT_HLE_EVENT: case THREADSTATUS_WAIT_SLEEP: case THREADSTATUS_WAIT_IPC: + case THREADSTATUS_WAIT_MUTEX: break; case THREADSTATUS_READY: @@ -178,11 +191,11 @@ void Thread::ResumeFromWait() { return; case THREADSTATUS_RUNNING: - DEBUG_ASSERT_MSG(false, "Thread with object id %u has already resumed.", GetObjectId()); + DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId()); return; case THREADSTATUS_DEAD: // This should never happen, as threads must complete before being stopped. - DEBUG_ASSERT_MSG(false, "Thread with object id %u cannot be resumed because it's DEAD.", + DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.", GetObjectId()); return; } @@ -190,8 +203,37 @@ void Thread::ResumeFromWait() { wakeup_callback = nullptr; status = THREADSTATUS_READY; - Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority); - Core::System::GetInstance().PrepareReschedule(); + + boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask); + if (!new_processor_id) { + new_processor_id = processor_id; + } + if (ideal_core != -1 && + Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) { + new_processor_id = ideal_core; + } + + ASSERT(*new_processor_id < 4); + + // Add thread to new core's scheduler + auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id); + + if (*new_processor_id != processor_id) { + // Remove thread from previous core's scheduler + scheduler->RemoveThread(this); + next_scheduler->AddThread(this, current_priority); + } + + processor_id = *new_processor_id; + + // If the thread was ready, unschedule from the previous core and schedule on the new core + scheduler->UnscheduleThread(this, current_priority); + next_scheduler->ScheduleThread(this, current_priority); + + // Change thread's scheduler + scheduler = next_scheduler; + + Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); } /** @@ -242,27 +284,25 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, SharedPtr<Process> owner_process) { // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { - LOG_ERROR(Kernel_SVC, "Invalid thread priority: %u", priority); + NGLOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); return ERR_OUT_OF_RANGE; } if (processor_id > THREADPROCESSORID_MAX) { - LOG_ERROR(Kernel_SVC, "Invalid processor id: %d", processor_id); + NGLOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id); return ERR_OUT_OF_RANGE_KERNEL; } // TODO(yuriks): Other checks, returning 0xD9001BEA if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { - LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %016" PRIx64, name.c_str(), entry_point); + NGLOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); // TODO (bunnei): Find the correct error code to use here return ResultCode(-1); } SharedPtr<Thread> thread(new Thread); - Core::System::GetInstance().Scheduler().AddThread(thread, priority); - thread->thread_id = NewThreadId(); thread->status = THREADSTATUS_DORMANT; thread->entry_point = entry_point; @@ -270,11 +310,17 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->nominal_priority = thread->current_priority = priority; thread->last_running_ticks = CoreTiming::GetTicks(); thread->processor_id = processor_id; + thread->ideal_core = processor_id; + thread->affinity_mask = 1ULL << processor_id; thread->wait_objects.clear(); - thread->wait_address = 0; + thread->mutex_wait_address = 0; + thread->condvar_wait_address = 0; + thread->wait_handle = 0; thread->name = std::move(name); thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap(); thread->owner_process = owner_process; + thread->scheduler = Core::System().GetInstance().Scheduler(processor_id); + thread->scheduler->AddThread(thread, priority); // Find the next available TLS index, and mark it as used auto& tls_slots = owner_process->tls_slots; @@ -291,8 +337,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, auto& linheap_memory = memory_region->linear_heap_memory; if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) { - LOG_ERROR(Kernel_SVC, - "Not enough space in region to allocate a new TLS page for thread"); + NGLOG_ERROR(Kernel_SVC, + "Not enough space in region to allocate a new TLS page for thread"); return ERR_OUT_OF_MEMORY; } @@ -314,7 +360,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, // TODO(Subv): Find the correct MemoryState for this region. vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, linheap_memory, offset, Memory::PAGE_SIZE, - MemoryState::ThreadLocalStorage); + MemoryState::ThreadLocal); } // Mark the slot as used @@ -332,32 +378,23 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, void Thread::SetPriority(u32 priority) { ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, "Invalid priority value."); - Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); - nominal_priority = current_priority = priority; -} - -void Thread::UpdatePriority() { - u32 best_priority = nominal_priority; - for (auto& mutex : held_mutexes) { - if (mutex->priority < best_priority) - best_priority = mutex->priority; - } - BoostPriority(best_priority); + nominal_priority = priority; + UpdatePriority(); } void Thread::BoostPriority(u32 priority) { - Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); + scheduler->SetThreadPriority(this, priority); current_priority = priority; } SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority, SharedPtr<Process> owner_process) { // Setup page table so we can write to memory - SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); // Initialize new "main" thread auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, - Memory::HEAP_VADDR_END, owner_process); + Memory::STACK_AREA_VADDR_END, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); @@ -392,13 +429,86 @@ VAddr Thread::GetCommandBufferAddress() const { return GetTLSAddress() + CommandHeaderOffset; } +void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { + thread->lock_owner = this; + wait_mutex_threads.emplace_back(std::move(thread)); + UpdatePriority(); +} + +void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) { + boost::remove_erase(wait_mutex_threads, thread); + thread->lock_owner = nullptr; + UpdatePriority(); +} + +void Thread::UpdatePriority() { + // Find the highest priority among all the threads that are waiting for this thread's lock + u32 new_priority = nominal_priority; + for (const auto& thread : wait_mutex_threads) { + if (thread->nominal_priority < new_priority) + new_priority = thread->nominal_priority; + } + + if (new_priority == current_priority) + return; + + scheduler->SetThreadPriority(this, new_priority); + + current_priority = new_priority; + + // Recursively update the priority of the thread that depends on the priority of this one. + if (lock_owner) + lock_owner->UpdatePriority(); +} + +void Thread::ChangeCore(u32 core, u64 mask) { + ideal_core = core; + affinity_mask = mask; + + if (status != THREADSTATUS_READY) { + return; + } + + boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)}; + + if (!new_processor_id) { + new_processor_id = processor_id; + } + if (ideal_core != -1 && + Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) { + new_processor_id = ideal_core; + } + + ASSERT(*new_processor_id < 4); + + // Add thread to new core's scheduler + auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id); + + if (*new_processor_id != processor_id) { + // Remove thread from previous core's scheduler + scheduler->RemoveThread(this); + next_scheduler->AddThread(this, current_priority); + } + + processor_id = *new_processor_id; + + // If the thread was ready, unschedule from the previous core and schedule on the new core + scheduler->UnscheduleThread(this, current_priority); + next_scheduler->ScheduleThread(this, current_priority); + + // Change thread's scheduler + scheduler = next_scheduler; + + Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /** * Gets the current thread */ Thread* GetCurrentThread() { - return Core::System::GetInstance().Scheduler().GetCurrentThread(); + return Core::System::GetInstance().CurrentScheduler().GetCurrentThread(); } void ThreadingInit() { @@ -406,6 +516,8 @@ void ThreadingInit() { next_thread_id = 1; } -void ThreadingShutdown() {} +void ThreadingShutdown() { + Kernel::ClearProcessList(); +} } // namespace Kernel diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 4fd2fc2f8..1d2da6d50 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -4,6 +4,7 @@ #pragma once +#include <memory> #include <string> #include <unordered_map> #include <vector> @@ -18,7 +19,7 @@ enum ThreadPriority : u32 { THREADPRIO_HIGHEST = 0, ///< Highest thread priority THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps - THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps + THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps THREADPRIO_LOWEST = 63, ///< Lowest thread priority }; @@ -38,11 +39,12 @@ enum ThreadProcessorId : s32 { enum ThreadStatus { THREADSTATUS_RUNNING, ///< Currently running THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_HLE_EVENT, ///< Waiting for hle event to finish THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc THREADSTATUS_DORMANT, ///< Created but not yet made ready THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; @@ -54,8 +56,8 @@ enum class ThreadWakeupReason { namespace Kernel { -class Mutex; class Process; +class Scheduler; class Thread final : public WaitObject { public: @@ -104,17 +106,23 @@ public: void SetPriority(u32 priority); /** - * Boost's a thread's priority to the best priority among the thread's held mutexes. - * This prevents priority inversion via priority inheritance. - */ - void UpdatePriority(); - - /** * Temporarily boosts the thread's priority until the next time it is scheduled * @param priority The new priority */ void BoostPriority(u32 priority); + /// Adds a thread to the list of threads that are waiting for a lock held by this thread. + void AddMutexWaiter(SharedPtr<Thread> thread); + + /// Removes a thread from the list of threads that are waiting for a lock held by this thread. + void RemoveMutexWaiter(SharedPtr<Thread> thread); + + /// Recalculates the current priority taking into account priority inheritance. + void UpdatePriority(); + + /// Changes the core that the thread is running or scheduled to run on. + void ChangeCore(u32 core, u64 mask); + /** * Gets the thread's thread ID * @return The thread's ID @@ -205,19 +213,22 @@ public: VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread - /// Mutexes currently held by this thread, which will be released when it exits. - boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; - - /// Mutexes that this thread is currently waiting for. - boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; - SharedPtr<Process> owner_process; ///< Process that owns this thread /// Objects that the thread is waiting on, in the same order as they were // passed to WaitSynchronization1/N. std::vector<SharedPtr<WaitObject>> wait_objects; - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address + /// List of threads that are waiting for a mutex that is held by this thread. + std::vector<SharedPtr<Thread>> wait_mutex_threads; + + /// Thread that owns the lock that this thread is waiting for. + SharedPtr<Thread> lock_owner; + + // If waiting on a ConditionVariable, this is the ConditionVariable address + VAddr condvar_wait_address; + VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address + Handle wait_handle; ///< The handle used to wait for the mutex. std::string name; @@ -234,6 +245,11 @@ public: // available. In case of a timeout, the object will be nullptr. std::function<WakeupCallback> wakeup_callback; + std::shared_ptr<Scheduler> scheduler; + + u32 ideal_core{0xFFFFFFFF}; + u64 affinity_mask{0x1}; + private: Thread(); ~Thread() override; diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 8da745634..661356a97 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -57,7 +57,8 @@ void Timer::Set(s64 initial, s64 interval) { // Immediately invoke the callback Signal(0); } else { - CoreTiming::ScheduleEvent(nsToCycles(initial), timer_callback_event_type, callback_handle); + CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), timer_callback_event_type, + callback_handle); } } @@ -77,7 +78,7 @@ void Timer::WakeupAllWaitingThreads() { } void Timer::Signal(int cycles_late) { - LOG_TRACE(Kernel, "Timer %u fired", GetObjectId()); + NGLOG_TRACE(Kernel, "Timer {} fired", GetObjectId()); signaled = true; @@ -86,7 +87,7 @@ void Timer::Signal(int cycles_late) { if (interval_delay != 0) { // Reschedule the timer with the interval delay - CoreTiming::ScheduleEvent(nsToCycles(interval_delay) - cycles_late, + CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(interval_delay) - cycles_late, timer_callback_event_type, callback_handle); } } @@ -97,7 +98,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle)); if (timer == nullptr) { - LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08" PRIx64, timer_handle); + NGLOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle); return; } diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index d5b36d71a..676e5b282 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cinttypes> #include <iterator> #include "common/assert.h" #include "common/logging/log.h" @@ -18,8 +17,26 @@ namespace Kernel { static const char* GetMemoryStateName(MemoryState state) { static const char* names[] = { - "Free", "Reserved", "IO", "Static", "Code", "Private", - "Shared", "Continuous", "Aliased", "Alias", "AliasCode", "Locked", + "Unmapped", + "Io", + "Normal", + "CodeStatic", + "CodeMutable", + "Heap", + "Shared", + "Unknown1" + "ModuleCodeStatic", + "ModuleCodeMutable", + "IpcBuffer0", + "Mapped", + "ThreadLocal", + "TransferMemoryIsolated", + "TransferMemory", + "ProcessMemory", + "Unknown2" + "IpcBuffer1", + "IpcBuffer3", + "KernelStack", }; return names[(int)state]; @@ -87,8 +104,15 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - Core::CPU().MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); + auto& system = Core::System::GetInstance(); + system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset, + VMAPermission::ReadWriteExecute); + system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset, + VMAPermission::ReadWriteExecute); final_vma.type = VMAType::AllocatedMemoryBlock; final_vma.permissions = VMAPermission::ReadWrite; @@ -109,7 +133,11 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - Core::CPU().MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); + auto& system = Core::System::GetInstance(); + system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); + system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); + system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); + system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); final_vma.type = VMAType::BackingMemory; final_vma.permissions = VMAPermission::ReadWrite; @@ -142,7 +170,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { VirtualMemoryArea& vma = vma_handle->second; vma.type = VMAType::Free; vma.permissions = VMAPermission::None; - vma.meminfo_state = MemoryState::Free; + vma.meminfo_state = MemoryState::Unmapped; vma.backing_block = nullptr; vma.offset = 0; @@ -166,6 +194,13 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) { } ASSERT(FindVMA(target)->second.size >= size); + + auto& system = Core::System::GetInstance(); + system.ArmInterface(0).UnmapMemory(target, size); + system.ArmInterface(1).UnmapMemory(target, size); + system.ArmInterface(2).UnmapMemory(target, size); + system.ArmInterface(3).UnmapMemory(target, size); + return RESULT_SUCCESS; } @@ -204,11 +239,10 @@ void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) { } } -void VMManager::LogLayout(Log::Level log_level) const { +void VMManager::LogLayout() const { for (const auto& p : vma_map) { const VirtualMemoryArea& vma = p.second; - LOG_GENERIC(Log::Class::Kernel, log_level, - "%016" PRIx64 " - %016" PRIx64 " size: %16" PRIx64 " %c%c%c %s", vma.base, + NGLOG_DEBUG(Kernel, "{:016X} - {:016X} size: {:016X} {}{}{} {}", vma.base, vma.base + vma.size, vma.size, (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-', (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-', @@ -224,8 +258,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) { } ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size); - ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, base); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size); + ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", base); VMAIter vma_handle = StripIterConstness(FindVMA(base)); if (vma_handle == vma_map.end()) { @@ -260,8 +294,8 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) { } ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size); - ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, target); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size); + ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", target); VAddr target_end = target + size; ASSERT(target_end >= target); @@ -358,38 +392,23 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { } u64 VMManager::GetTotalMemoryUsage() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return 0xBE000000; + NGLOG_WARNING(Kernel, "(STUBBED) called"); + return 0xF8000000; } u64 VMManager::GetTotalHeapUsage() { - LOG_WARNING(Kernel, "(STUBBED) called"); + NGLOG_WARNING(Kernel, "(STUBBED) called"); return 0x0; } VAddr VMManager::GetAddressSpaceBaseAddr() { - LOG_WARNING(Kernel, "(STUBBED) called"); + NGLOG_WARNING(Kernel, "(STUBBED) called"); return 0x8000000; } u64 VMManager::GetAddressSpaceSize() { - LOG_WARNING(Kernel, "(STUBBED) called"); + NGLOG_WARNING(Kernel, "(STUBBED) called"); return MAX_ADDRESS; } -VAddr VMManager::GetMapRegionBaseAddr() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return Memory::HEAP_VADDR; -} - -VAddr VMManager::GetNewMapRegionBaseAddr() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return 0x8000000; -} - -u64 VMManager::GetNewMapRegionSize() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return 0x8000000; -} - } // namespace Kernel diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 8de704a60..38e4ebcd3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -41,15 +41,24 @@ enum class VMAPermission : u8 { /// Set of values returned in MemoryInfo.state by svcQueryMemory. enum class MemoryState : u32 { - Free = 0, - IO = 1, - Normal = 2, - Code = 3, - Static = 4, - Heap = 5, - Shared = 6, - Mapped = 6, - ThreadLocalStorage = 12, + Unmapped = 0x0, + Io = 0x1, + Normal = 0x2, + CodeStatic = 0x3, + CodeMutable = 0x4, + Heap = 0x5, + Shared = 0x6, + ModuleCodeStatic = 0x8, + ModuleCodeMutable = 0x9, + IpcBuffer0 = 0xA, + Mapped = 0xB, + ThreadLocal = 0xC, + TransferMemoryIsolated = 0xD, + TransferMemory = 0xE, + ProcessMemory = 0xF, + IpcBuffer1 = 0x11, + IpcBuffer3 = 0x12, + KernelStack = 0x13, }; /** @@ -66,7 +75,7 @@ struct VirtualMemoryArea { VMAType type = VMAType::Free; VMAPermission permissions = VMAPermission::None; /// Tag returned by svcQueryMemory. Not otherwise used. - MemoryState meminfo_state = MemoryState::Free; + MemoryState meminfo_state = MemoryState::Unmapped; // Settings for type = AllocatedMemoryBlock /// Memory block backing this VMA. @@ -178,7 +187,7 @@ public: void RefreshMemoryBlockMappings(const std::vector<u8>* block); /// Dumps the address space layout to the log, for debugging - void LogLayout(Log::Level log_level) const; + void LogLayout() const; /// Gets the total memory usage, used by svcGetInfo u64 GetTotalMemoryUsage(); @@ -192,15 +201,6 @@ public: /// Gets the total address space address size, used by svcGetInfo u64 GetAddressSpaceSize(); - /// Gets the map region base address, used by svcGetInfo - VAddr GetMapRegionBaseAddr(); - - /// Gets the base address for a new memory region, used by svcGetInfo - VAddr GetNewMapRegionBaseAddr(); - - /// Gets the size for a new memory region, used by svcGetInfo - u64 GetNewMapRegionSize(); - /// Each VMManager has its own page table, which is set as the main one when the owning process /// is scheduled. Memory::PageTable page_table; diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index ec147b84c..b08ac72c1 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp @@ -39,7 +39,8 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { for (const auto& thread : waiting_threads) { // The list of waiting threads must not contain threads that are not waiting to be awakened. ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || + thread->status == THREADSTATUS_WAIT_HLE_EVENT, "Inconsistent thread statuses in waiting_threads"); if (thread->current_priority >= candidate_priority) diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 656e1b4a7..3ebf7aadf 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -32,7 +32,8 @@ enum class ErrorModule : u32 { Common = 0, Kernel = 1, FS = 2, - NvidiaTransferMemory = 3, + OS = 3, // used for Memory, Thread, Mutex, Nvidia + HTCS = 4, NCM = 5, DD = 6, LR = 8, @@ -42,41 +43,80 @@ enum class ErrorModule : u32 { PM = 15, NS = 16, HTC = 18, + NCMContent = 20, SM = 21, RO = 22, SDMMC = 24, + OVLN = 25, SPL = 26, ETHC = 100, I2C = 101, + GPIO = 102, + UART = 103, Settings = 105, + WLAN = 107, + XCD = 108, NIFM = 110, - Display = 114, - NTC = 116, + Hwopus = 111, + Bluetooth = 113, + VI = 114, + NFP = 115, + Time = 116, FGM = 117, - PCIE = 120, + OE = 118, + PCIe = 120, Friends = 121, + BCAT = 122, SSL = 123, Account = 124, + News = 125, Mii = 126, + NFC = 127, AM = 128, PlayReport = 129, + AHID = 130, + Qlaunch = 132, PCV = 133, OMM = 134, + BPC = 135, + PSM = 136, NIM = 137, PSC = 138, + TC = 139, USB = 140, + NSD = 141, + PCTL = 142, BTM = 143, + ETicket = 145, + NGC = 146, ERPT = 147, APM = 148, + Profiler = 150, + ErrorUpload = 151, + Audio = 153, NPNS = 154, + NPNSHTTPSTREAM = 155, ARP = 157, - BOOT = 158, - NFC = 161, + SWKBD = 158, + BOOT = 159, + NFCMifare = 161, UserlandAssert = 162, + Fatal = 163, + NIMShop = 164, + SPSM = 165, + BGTC = 167, UserlandCrash = 168, - HID = 203, + SREPO = 180, + Dauth = 181, + HID = 202, + LDN = 203, + Irsensor = 205, Capture = 206, - TC = 651, + Manu = 208, + ATK = 209, + GRC = 212, + Migration = 216, + MigrationLdcServ = 217, GeneralWebApplet = 800, WifiWebAuthApplet = 809, WhitelistedApplet = 810, @@ -108,11 +148,11 @@ union ResultCode { } constexpr bool IsSuccess() const { - return is_error.ExtractValue(raw) == 0; + return raw == 0; } constexpr bool IsError() const { - return is_error.ExtractValue(raw) == 1; + return raw != 0; } }; @@ -200,6 +240,9 @@ public: } ResultVal& operator=(const ResultVal& o) { + if (this == &o) { + return *this; + } if (!empty()) { if (!o.empty()) { object = o.object; diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 5716577d6..f2fffa760 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -2,15 +2,149 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.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" -namespace Service { -namespace Account { +namespace Service::Account { + +// TODO: RE this structure +struct UserData { + INSERT_PADDING_WORDS(1); + u32 icon_id; + u8 bg_color_id; + INSERT_PADDING_BYTES(0x7); + INSERT_PADDING_BYTES(0x10); + INSERT_PADDING_BYTES(0x60); +}; +static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); + +struct ProfileBase { + u8 user_id[0x10]; + u64 timestamp; + u8 username[0x20]; +}; +static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); + +using Uid = std::array<u64, 2>; +static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull}; + +class IProfile final : public ServiceFramework<IProfile> { +public: + IProfile() : ServiceFramework("IProfile") { + static const FunctionInfo functions[] = { + {0, nullptr, "Get"}, + {1, &IProfile::GetBase, "GetBase"}, + {10, nullptr, "GetImageSize"}, + {11, nullptr, "LoadImage"}, + }; + RegisterHandlers(functions); + } + +private: + void GetBase(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + ProfileBase profile_base{}; + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); + } +}; + +class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { +public: + IManagerForApplication() : ServiceFramework("IManagerForApplication") { + static const FunctionInfo functions[] = { + {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, + {1, &IManagerForApplication::GetAccountId, "GetAccountId"}, + {2, nullptr, "EnsureIdTokenCacheAsync"}, + {3, nullptr, "LoadIdTokenCache"}, + {130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"}, + {150, nullptr, "CreateAuthorizationRequest"}, + {160, nullptr, "StoreOpenContext"}, + }; + RegisterHandlers(functions); + } + +private: + void CheckAvailability(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(true); // TODO: Check when this is supposed to return true and when not + } + + void GetAccountId(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0x12345678ABCDEF); + } +}; + +void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(true); // TODO: Check when this is supposed to return true and when not +} + +void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; + ctx.WriteBuffer(user_ids.data(), user_ids.size()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; + ctx.WriteBuffer(user_ids.data(), user_ids.size()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IProfile>(); + NGLOG_DEBUG(Service_ACC, "called"); +} + +void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IManagerForApplication>(); + NGLOG_DEBUG(Service_ACC, "called"); +} + +void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { + NGLOG_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, const char* name) + : ServiceFramework(name), module(std::move(module)) {} void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<ACC_U0>()->InstallAsService(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); } -} // namespace Account -} // namespace Service +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 44d024f48..58f8d260c 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -6,11 +6,28 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Account { +namespace Service::Account { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void GetUserExistence(Kernel::HLERequestContext& ctx); + void ListAllUsers(Kernel::HLERequestContext& ctx); + void ListOpenUsers(Kernel::HLERequestContext& ctx); + void GetLastOpenedUser(Kernel::HLERequestContext& ctx); + void GetProfile(Kernel::HLERequestContext& ctx); + void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); + void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; /// Registers all ACC services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace Account -} // namespace Service +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp new file mode 100644 index 000000000..280b3e464 --- /dev/null +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/acc/acc_aa.h" + +namespace Service::Account { + +ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") { + static const FunctionInfo functions[] = { + {0, nullptr, "EnsureCacheAsync"}, + {1, nullptr, "LoadCache"}, + {2, nullptr, "GetDeviceAccountId"}, + {50, nullptr, "RegisterNotificationTokenAsync"}, + {51, nullptr, "UnregisterNotificationTokenAsync"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h new file mode 100644 index 000000000..796f7ef85 --- /dev/null +++ b/src/core/hle/service/acc/acc_aa.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/acc/acc.h" + +namespace Service::Account { + +class ACC_AA final : public Module::Interface { +public: + explicit ACC_AA(std::shared_ptr<Module> module); +}; + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp new file mode 100644 index 000000000..9ffb40b22 --- /dev/null +++ b/src/core/hle/service/acc/acc_su.cpp @@ -0,0 +1,53 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/acc/acc_su.h" + +namespace Service::Account { + +ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetUserCount"}, + {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, + {2, &ACC_SU::ListAllUsers, "ListAllUsers"}, + {3, &ACC_SU::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_SU::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {51, nullptr, "TrySelectUserWithoutInteraction"}, + {60, nullptr, "ListOpenContextStoredUsers"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, nullptr, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {104, nullptr, "GetProfileUpdateNotifier"}, + {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {110, nullptr, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {190, nullptr, "GetUserLastOpenedApplication"}, + {191, nullptr, "ActivateOpenContextHolder"}, + {200, nullptr, "BeginUserRegistration"}, + {201, nullptr, "CompleteUserRegistration"}, + {202, nullptr, "CancelUserRegistration"}, + {203, nullptr, "DeleteUser"}, + {204, nullptr, "SetUserPosition"}, + {205, nullptr, "GetProfileEditor"}, + {206, nullptr, "CompleteUserRegistrationForcibly"}, + {210, nullptr, "CreateFloatingRegistrationRequest"}, + {230, nullptr, "AuthenticateServiceAsync"}, + {250, nullptr, "GetBaasAccountAdministrator"}, + {290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"}, + {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, + {299, nullptr, "SuspendBackgroundDaemon"}, + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h new file mode 100644 index 000000000..3894a6991 --- /dev/null +++ b/src/core/hle/service/acc/acc_su.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/acc/acc.h" + +namespace Service { +namespace Account { + +class ACC_SU final : public Module::Interface { +public: + explicit ACC_SU(std::shared_ptr<Module> module); +}; + +} // namespace Account +} // namespace Service diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 52c3491d5..44e21ac09 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -2,123 +2,32 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" #include "core/hle/service/acc/acc_u0.h" -namespace Service { -namespace Account { +namespace Service::Account { -using Uid = std::array<u64, 2>; -static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull}; - -class IProfile final : public ServiceFramework<IProfile> { -public: - IProfile() : ServiceFramework("IProfile") { - static const FunctionInfo functions[] = { - {1, &IProfile::GetBase, "GetBase"}, - }; - RegisterHandlers(functions); - } - -private: - void GetBase(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - ProfileBase profile_base{}; - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); - } -}; - -class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { -public: - IManagerForApplication() : ServiceFramework("IManagerForApplication") { - static const FunctionInfo functions[] = { - {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, - {1, &IManagerForApplication::GetAccountId, "GetAccountId"}, - }; - RegisterHandlers(functions); - } - -private: - void CheckAvailability(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(true); // TODO: Check when this is supposed to return true and when not - } - - void GetAccountId(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0x12345678ABCDEF); - } -}; - -void ACC_U0::GetUserExistence(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(true); // TODO: Check when this is supposed to return true and when not -} - -void ACC_U0::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids.data(), user_ids.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void ACC_U0::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids.data(), user_ids.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void ACC_U0::GetProfile(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfile>(); - LOG_DEBUG(Service_ACC, "called"); -} - -void ACC_U0::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void ACC_U0::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IManagerForApplication>(); - LOG_DEBUG(Service_ACC, "called"); -} - -void ACC_U0::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(DEFAULT_USER_ID); -} - -ACC_U0::ACC_U0() : ServiceFramework("acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") { static const FunctionInfo functions[] = { + {0, nullptr, "GetUserCount"}, {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, {2, &ACC_U0::ListAllUsers, "ListAllUsers"}, {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"}, {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U0::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {51, nullptr, "TrySelectUserWithoutInteraction"}, + {60, nullptr, "ListOpenContextStoredUsers"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, + {102, nullptr, "AuthenticateApplicationAsync"}, + {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {110, nullptr, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {120, nullptr, "CreateGuestLoginRequest"}, + {130, nullptr, "LoadOpenContext"}, }; RegisterHandlers(functions); } -} // namespace Account -} // namespace Service +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index 222f37282..6ded596b3 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -4,37 +4,13 @@ #pragma once -#include "core/hle/service/service.h" +#include "core/hle/service/acc/acc.h" -namespace Service { -namespace Account { +namespace Service::Account { -// TODO: RE this structure -struct UserData { - INSERT_PADDING_BYTES(0x80); -}; -static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); - -// TODO: RE this structure -struct ProfileBase { - INSERT_PADDING_BYTES(0x38); -}; -static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); - -class ACC_U0 final : public ServiceFramework<ACC_U0> { +class ACC_U0 final : public Module::Interface { public: - ACC_U0(); - ~ACC_U0() = default; - -private: - void GetUserExistence(Kernel::HLERequestContext& ctx); - void ListAllUsers(Kernel::HLERequestContext& ctx); - void ListOpenUsers(Kernel::HLERequestContext& ctx); - void GetLastOpenedUser(Kernel::HLERequestContext& ctx); - void GetProfile(Kernel::HLERequestContext& ctx); - void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); - void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); + explicit ACC_U0(std::shared_ptr<Module> module); }; -} // namespace Account -} // namespace Service +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp new file mode 100644 index 000000000..d101d4e0d --- /dev/null +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/acc/acc_u1.h" + +namespace Service::Account { + +ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetUserCount"}, + {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, + {2, &ACC_U1::ListAllUsers, "ListAllUsers"}, + {3, &ACC_U1::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_U1::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {51, nullptr, "TrySelectUserWithoutInteraction"}, + {60, nullptr, "ListOpenContextStoredUsers"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, nullptr, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetProfileUpdateNotifier"}, + {104, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {105, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {110, nullptr, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {190, nullptr, "GetUserLastOpenedApplication"}, + {191, nullptr, "ActivateOpenContextHolder"}, + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h new file mode 100644 index 000000000..5e3e7659b --- /dev/null +++ b/src/core/hle/service/acc/acc_u1.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/acc/acc.h" + +namespace Service::Account { + +class ACC_U1 final : public Module::Interface { +public: + explicit ACC_U1(std::shared_ptr<Module> module); +}; + +} // namespace Service::Account diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d3a674cf6..b8d6b8d4d 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,34 +2,42 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> +#include <stack> +#include "core/file_sys/filesystem.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/apm/apm.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/set/set.h" +#include "core/settings.h" -namespace Service { -namespace AM { +namespace Service::AM { IWindowController::IWindowController() : ServiceFramework("IWindowController") { static const FunctionInfo functions[] = { + {0, nullptr, "CreateWindow"}, {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"}, {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"}, + {11, nullptr, "ReleaseForegroundRights"}, + {12, nullptr, "RejectToChangeIntoBackground"}, }; RegisterHandlers(functions); } void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(0); } void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -48,34 +56,70 @@ IAudioController::IAudioController() : ServiceFramework("IAudioController") { } void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(volume); } void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(volume); } -IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {} +IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetLastForegroundCaptureImage"}, + {1, nullptr, "UpdateLastForegroundCaptureImage"}, + {2, nullptr, "GetLastApplicationCaptureImage"}, + {3, nullptr, "GetCallerAppletCaptureImage"}, + {4, nullptr, "UpdateCallerAppletCaptureImage"}, + {5, nullptr, "GetLastForegroundCaptureImageEx"}, + {6, nullptr, "GetLastApplicationCaptureImageEx"}, + {7, nullptr, "GetCallerAppletCaptureImageEx"}, + {8, nullptr, "TakeScreenShotOfOwnLayer"}, // 2.0.0+ + {9, nullptr, "CopyBetweenCaptureBuffers"}, // 5.0.0+ + {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, + {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, + {12, nullptr, "AcquireLastForegroundCaptureBuffer"}, + {13, nullptr, "ReleaseLastForegroundCaptureBuffer"}, + {14, nullptr, "AcquireCallerAppletCaptureBuffer"}, + {15, nullptr, "ReleaseCallerAppletCaptureBuffer"}, + {16, nullptr, "AcquireLastApplicationCaptureBufferEx"}, + {17, nullptr, "AcquireLastForegroundCaptureBufferEx"}, + {18, nullptr, "AcquireCallerAppletCaptureBufferEx"}, + // 2.0.0+ + {20, nullptr, "ClearCaptureBuffer"}, + {21, nullptr, "ClearAppletTransitionBuffer"}, + // 4.0.0+ + {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, + {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, + {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, + {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, + {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"}, + {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"}, + }; + RegisterHandlers(functions); +} IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {} ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { static const FunctionInfo functions[] = { + {0, nullptr, "Exit"}, {1, &ISelfController::LockExit, "LockExit"}, {2, &ISelfController::UnlockExit, "UnlockExit"}, + {3, nullptr, "EnterFatalSection"}, + {4, nullptr, "LeaveFatalSection"}, {9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"}, {10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"}, {11, &ISelfController::SetOperationModeChangedNotification, @@ -84,13 +128,34 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger "SetPerformanceModeChangedNotification"}, {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"}, {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"}, + {15, nullptr, "SetScreenShotAppletIdentityInfo"}, {16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"}, + {17, nullptr, "SetControllerFirmwareUpdateSection"}, + {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"}, + {19, nullptr, "SetScreenShotImageOrientation"}, + {20, nullptr, "SetDesirableKeyboardLayout"}, {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, + {41, nullptr, "IsSystemBufferSharingEnabled"}, + {42, nullptr, "GetSystemSharedLayerHandle"}, + {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, + {51, nullptr, "ApproveToDisplay"}, + {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, + {61, nullptr, "SetMediaPlaybackState"}, + {62, nullptr, "SetIdleTimeDetectionExtension"}, + {63, nullptr, "GetIdleTimeDetectionExtension"}, + {64, nullptr, "SetInputDetectionSourceSet"}, + {65, nullptr, "ReportUserIsActive"}, + {66, nullptr, "GetCurrentIlluminance"}, + {67, nullptr, "IsIlluminanceAvailable"}, + {68, nullptr, "SetAutoSleepDisabled"}, + {69, nullptr, "IsAutoSleepDisabled"}, + {70, nullptr, "ReportMultimediaError"}, + {80, nullptr, "SetWirelessPriorityMode"}, }; RegisterHandlers(functions); launchable_event = - Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent"); + Kernel::Event::Create(Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent"); } void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { @@ -109,14 +174,14 @@ void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) { @@ -127,14 +192,14 @@ void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestCo IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag)); + NGLOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); } void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) { @@ -145,7 +210,7 @@ void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestCont IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag)); + NGLOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); } void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) { @@ -158,21 +223,21 @@ void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called enabled=%u", static_cast<u32>(enabled)); + NGLOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled); } void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) { @@ -182,7 +247,7 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(launchable_event); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) { @@ -195,16 +260,44 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) rb.Push(RESULT_SUCCESS); rb.Push(layer_id); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"}, + {2, nullptr, "GetThisAppletKind"}, + {3, nullptr, "AllowToEnterSleep"}, + {4, nullptr, "DisallowToEnterSleep"}, {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"}, {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"}, + {7, nullptr, "GetCradleStatus"}, + {8, nullptr, "GetBootMode"}, {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, + {10, nullptr, "RequestToAcquireSleepLock"}, + {11, nullptr, "ReleaseSleepLock"}, + {12, nullptr, "ReleaseSleepLockTransiently"}, + {13, nullptr, "GetAcquiredSleepLockEvent"}, + {20, nullptr, "PushToGeneralChannel"}, + {30, nullptr, "GetHomeButtonReaderLockAccessor"}, + {31, nullptr, "GetReaderLockAccessorEx"}, + {40, nullptr, "GetCradleFwVersion"}, + {50, nullptr, "IsVrModeEnabled"}, + {51, nullptr, "SetVrModeEnabled"}, + {52, nullptr, "SwitchLcdBacklight"}, + {55, nullptr, "IsInControllerFirmwareUpdateSection"}, + {60, nullptr, "GetDefaultDisplayResolution"}, + {61, nullptr, "GetDefaultDisplayResolutionChangeEvent"}, + {62, nullptr, "GetHdcpAuthenticationState"}, + {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"}, }; RegisterHandlers(functions); @@ -218,7 +311,7 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(event); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { @@ -226,7 +319,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push<u32>(15); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { @@ -234,44 +327,128 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u8>(FocusState::InFocus)); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u8>(OperationMode::Handheld)); + rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld)); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld)); + rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked + : APM::PerformanceMode::Handheld)); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } +class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +public: + explicit IStorageAccessor(std::vector<u8> buffer) + : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + static const FunctionInfo functions[] = { + {0, &IStorageAccessor::GetSize, "GetSize"}, + {10, &IStorageAccessor::Write, "Write"}, + {11, &IStorageAccessor::Read, "Read"}, + }; + RegisterHandlers(functions); + } + +private: + std::vector<u8> buffer; + + void GetSize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(buffer.size())); + + NGLOG_DEBUG(Service_AM, "called"); + } + + void Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const u64 offset{rp.Pop<u64>()}; + const std::vector<u8> data{ctx.ReadBuffer()}; + + ASSERT(offset + data.size() <= buffer.size()); + + std::memcpy(&buffer[offset], data.data(), data.size()); + + IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + rb.Push(RESULT_SUCCESS); + + NGLOG_DEBUG(Service_AM, "called, offset={}", offset); + } + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const u64 offset{rp.Pop<u64>()}; + const size_t size{ctx.GetWriteBufferSize()}; + + ASSERT(offset + size <= buffer.size()); + + ctx.WriteBuffer(buffer.data() + offset, size); + + IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + rb.Push(RESULT_SUCCESS); + + NGLOG_DEBUG(Service_AM, "called, offset={}", offset); + } +}; + +class IStorage final : public ServiceFramework<IStorage> { +public: + explicit IStorage(std::vector<u8> buffer) + : ServiceFramework("IStorage"), buffer(std::move(buffer)) { + static const FunctionInfo functions[] = { + {0, &IStorage::Open, "Open"}, + {1, nullptr, "OpenTransferStorage"}, + }; + RegisterHandlers(functions); + } + +private: + std::vector<u8> buffer; + + void Open(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + + NGLOG_DEBUG(Service_AM, "called"); + } +}; + class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { static const FunctionInfo functions[] = { {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, {1, nullptr, "IsCompleted"}, - {10, nullptr, "Start"}, + {10, &ILibraryAppletAccessor::Start, "Start"}, {20, nullptr, "RequestExit"}, {25, nullptr, "Terminate"}, - {30, nullptr, "GetResult"}, + {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, - {100, nullptr, "PushInData"}, - {101, nullptr, "PopOutData"}, + {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, + {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, {102, nullptr, "PushExtraStorage"}, {103, nullptr, "PushInteractiveInData"}, {104, nullptr, "PopInteractiveOutData"}, {105, nullptr, "GetPopOutDataEvent"}, {106, nullptr, "GetPopInteractiveOutDataEvent"}, - {120, nullptr, "NeedsToExitProcess"}, + {110, nullptr, "NeedsToExitProcess"}, {120, nullptr, "GetLibraryAppletInfo"}, {150, nullptr, "RequestForAppletToGetForeground"}, {160, nullptr, "GetIndirectLayerConsumerHandle"}, @@ -290,9 +467,44 @@ private: rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(state_changed_event); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); + } + + void GetResult(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + NGLOG_WARNING(Service_AM, "(STUBBED) called"); + } + + void Start(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + NGLOG_WARNING(Service_AM, "(STUBBED) called"); + } + + void PushInData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + storage_stack.push(rp.PopIpcInterface<AM::IStorage>()); + + IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + rb.Push(RESULT_SUCCESS); + + NGLOG_DEBUG(Service_AM, "called"); + } + + void PopOutData(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top())); + + storage_stack.pop(); + + NGLOG_DEBUG(Service_AM, "called"); } + std::stack<std::shared_ptr<AM::IStorage>> storage_stack; Kernel::SharedPtr<Kernel::Event> state_changed_event; }; @@ -301,7 +513,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, - {10, nullptr, "CreateStorage"}, + {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, {11, nullptr, "CreateTransferMemoryStorage"}, {12, nullptr, "CreateHandleStorage"}, }; @@ -314,83 +526,61 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<AM::ILibraryAppletAccessor>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } -class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { -public: - explicit IStorageAccessor(std::vector<u8> buffer) - : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { - static const FunctionInfo functions[] = { - {0, &IStorageAccessor::GetSize, "GetSize"}, - {11, &IStorageAccessor::Read, "Read"}, - }; - RegisterHandlers(functions); - } - -private: - std::vector<u8> buffer; - - void GetSize(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; - - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u64>(buffer.size())); - - LOG_DEBUG(Service_AM, "called"); - } - - void Read(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - u64 offset = rp.Pop<u64>(); - - const size_t size{ctx.GetWriteBufferSize()}; - - ASSERT(offset + size <= buffer.size()); - - ctx.WriteBuffer(buffer.data() + offset, size); - - IPC::ResponseBuilder rb{ctx, 2}; - - rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_AM, "called"); - } -}; - -class IStorage final : public ServiceFramework<IStorage> { -public: - explicit IStorage(std::vector<u8> buffer) - : ServiceFramework("IStorage"), buffer(std::move(buffer)) { - static const FunctionInfo functions[] = { - {0, &IStorage::Open, "Open"}, - }; - RegisterHandlers(functions); - } - -private: - std::vector<u8> buffer; - - void Open(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; +void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 size{rp.Pop<u64>()}; + std::vector<u8> buffer(size); - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 1)}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorage>(std::move(buffer)); - LOG_DEBUG(Service_AM, "called"); - } -}; + NGLOG_DEBUG(Service_AM, "called, size={}", size); +} IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, + {10, nullptr, "CreateApplicationAndPushAndRequestToStart"}, + {11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"}, + {12, nullptr, "CreateApplicationAndRequestToStart"}, + {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest, + "CreateApplicationAndRequestToStartForQuest"}, {20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"}, {21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"}, {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"}, + {23, &IApplicationFunctions::GetDisplayVersion, "GetDisplayVersion"}, + {24, nullptr, "GetLaunchStorageInfoForDebug"}, + {25, nullptr, "ExtendSaveData"}, + {26, nullptr, "GetSaveDataSize"}, + {30, nullptr, "BeginBlockingHomeButtonShortAndLongPressed"}, + {31, nullptr, "EndBlockingHomeButtonShortAndLongPressed"}, + {32, nullptr, "BeginBlockingHomeButton"}, + {33, nullptr, "EndBlockingHomeButton"}, + {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, + {50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"}, + {60, nullptr, "SetMediaPlaybackStateForApplication"}, + {65, nullptr, "IsGamePlayRecordingSupported"}, {66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"}, {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"}, - {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, + {68, nullptr, "RequestFlushGamePlayingMovieForDebug"}, + {70, nullptr, "RequestToShutdown"}, + {71, nullptr, "RequestToReboot"}, + {80, nullptr, "ExitAndRequestToShowThanksMessage"}, + {90, nullptr, "EnableApplicationCrashReport"}, + {100, nullptr, "InitializeApplicationCopyrightFrameBuffer"}, + {101, nullptr, "SetApplicationCopyrightImage"}, + {102, nullptr, "SetApplicationCopyrightVisibility"}, + {110, nullptr, "QueryApplicationPlayStatistics"}, + {120, nullptr, "ExecuteProgram"}, + {121, nullptr, "ClearUserChannel"}, + {122, nullptr, "UnpopToUserChannel"}, + {500, nullptr, "StartContinuousRecordingFlushForDebug"}, + {1000, nullptr, "CreateMovieMaker"}, + {1001, nullptr, "PrepareForJit"}, }; RegisterHandlers(functions); } @@ -412,13 +602,35 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<AM::IStorage>(buffer); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); +} + +void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( + Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u128 uid = rp.PopRaw<u128>(); + + NGLOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]); + IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); + + FileSys::Path unused; + auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused); + if (savedata.Failed()) { + // Create the save data and return an error indicating that the operation was performed. + FileSystem::FormatFileSystem(FileSystem::Type::SaveData); + // TODO(Subv): Find out the correct error code for this. + rb.Push(ResultCode(ErrorModule::FS, 40)); + } else { + rb.Push(RESULT_SUCCESS); + } + rb.Push<u64>(0); } @@ -432,27 +644,36 @@ void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called, result=0x%08X", result); + NGLOG_WARNING(Service_AM, "(STUBBED) called, result=0x{:08X}", result); +} + +void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(1); + rb.Push<u64>(0); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { + // TODO(bunnei): This should be configurable IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(SystemLanguage::English); - LOG_WARNING(Service_AM, "(STUBBED) called"); + rb.Push(static_cast<u64>(Service::Set::LanguageCode::EN_US)); + NGLOG_DEBUG(Service_AM, "called"); } void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) { @@ -460,7 +681,18 @@ void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); // Unknown, seems to be ignored by official processes - LOG_WARNING(Service_AM, "(STUBBED) called"); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + + // Returns a 128-bit UUID + rb.Push<u64>(0); + rb.Push<u64>(0); + + NGLOG_WARNING(Service_AM, "(STUBBED) called"); } void InstallInterfaces(SM::ServiceManager& service_manager, @@ -469,5 +701,64 @@ void InstallInterfaces(SM::ServiceManager& service_manager, std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager); } -} // namespace AM -} // namespace Service +IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") { + static const FunctionInfo functions[] = { + {10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"}, + {11, nullptr, "LockForeground"}, + {12, nullptr, "UnlockForeground"}, + {20, nullptr, "PopFromGeneralChannel"}, + {21, nullptr, "GetPopFromGeneralChannelEvent"}, + {30, nullptr, "GetHomeButtonWriterLockAccessor"}, + {31, nullptr, "GetWriterLockAccessorEx"}, + }; + RegisterHandlers(functions); +} + +void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_AM, "(STUBBED) called"); +} + +IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") { + static const FunctionInfo functions[] = { + {0, nullptr, "RequestToEnterSleep"}, + {1, nullptr, "EnterSleep"}, + {2, nullptr, "StartSleepSequence"}, + {3, nullptr, "StartShutdownSequence"}, + {4, nullptr, "StartRebootSequence"}, + {10, nullptr, "LoadAndApplyIdlePolicySettings"}, + {11, nullptr, "NotifyCecSettingsChanged"}, + {12, nullptr, "SetDefaultHomeButtonLongPressTime"}, + {13, nullptr, "UpdateDefaultDisplayResolution"}, + {14, nullptr, "ShouldSleepOnBoot"}, + {15, nullptr, "GetHdcpAuthenticationFailedEvent"}, + }; + RegisterHandlers(functions); +} + +IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") { + static const FunctionInfo functions[] = { + {0, nullptr, "CreateApplication"}, + {1, nullptr, "PopLaunchRequestedApplication"}, + {10, nullptr, "CreateSystemApplication"}, + {100, nullptr, "PopFloatingApplicationForDevelopment"}, + }; + RegisterHandlers(functions); +} + +IProcessWindingController::IProcessWindingController() + : ServiceFramework("IProcessWindingController") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetLaunchReason"}, + {11, nullptr, "OpenCallingLibraryApplet"}, + {21, nullptr, "PushContext"}, + {22, nullptr, "PopContext"}, + {23, nullptr, "CancelWindingReservation"}, + {30, nullptr, "WindAndDoReserved"}, + {40, nullptr, "ReserveToStartAndWaitAndUnwindThis"}, + {41, nullptr, "ReserveToStartAndWait"}, + }; + RegisterHandlers(functions); +} +} // namespace Service::AM diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 27dbd8c95..1da79fd01 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -18,10 +18,25 @@ class NVFlinger; namespace AM { -// TODO: Add more languages enum SystemLanguage { Japanese = 0, - English = 1, + English = 1, // en-US + French = 2, + German = 3, + Italian = 4, + Spanish = 5, + Chinese = 6, + Korean = 7, + Dutch = 8, + Portuguese = 9, + Russian = 10, + Taiwanese = 11, + BritishEnglish = 12, // en-GB + CanadianFrench = 13, + LatinAmericanSpanish = 14, // es-419 + // 4.0.0+ + SimplifiedChinese = 15, + TraditionalChinese = 16, }; class IWindowController final : public ServiceFramework<IWindowController> { @@ -70,6 +85,7 @@ private: void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx); void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); void SetScreenShotPermission(Kernel::HLERequestContext& ctx); + void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::SharedPtr<Kernel::Event> launchable_event; @@ -105,6 +121,7 @@ public: private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); + void CreateStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { @@ -113,12 +130,38 @@ public: private: void PopLaunchParameter(Kernel::HLERequestContext& ctx); + void CreateApplicationAndRequestToStartForQuest(Kernel::HLERequestContext& ctx); void EnsureSaveData(Kernel::HLERequestContext& ctx); void SetTerminateResult(Kernel::HLERequestContext& ctx); + void GetDisplayVersion(Kernel::HLERequestContext& ctx); void GetDesiredLanguage(Kernel::HLERequestContext& ctx); void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx); void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx); void NotifyRunning(Kernel::HLERequestContext& ctx); + void GetPseudoDeviceId(Kernel::HLERequestContext& ctx); +}; + +class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { +public: + IHomeMenuFunctions(); + +private: + void RequestToGetForeground(Kernel::HLERequestContext& ctx); +}; + +class IGlobalStateController final : public ServiceFramework<IGlobalStateController> { +public: + IGlobalStateController(); +}; + +class IApplicationCreator final : public ServiceFramework<IApplicationCreator> { +public: + IApplicationCreator(); +}; + +class IProcessWindingController final : public ServiceFramework<IProcessWindingController> { +public: + IProcessWindingController(); }; /// Registers all AM services with the specified service manager. diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 0e51caa70..7ce551de3 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -8,8 +8,7 @@ #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/nvflinger/nvflinger.h" -namespace Service { -namespace AM { +namespace Service::AM { class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> { public: @@ -21,6 +20,7 @@ public: {2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"}, {3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"}, {4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"}, + {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"}, {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, @@ -33,74 +33,188 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISelfController>(nvflinger); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetAudioController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetProcessWindingController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IProcessWindingController>(); + NGLOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationFunctions>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); + } + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; +}; + +class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> { +public: + explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)) { + static const FunctionInfo functions[] = { + {0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, + {1, &ISystemAppletProxy::GetSelfController, "GetSelfController"}, + {2, &ISystemAppletProxy::GetWindowController, "GetWindowController"}, + {3, &ISystemAppletProxy::GetAudioController, "GetAudioController"}, + {4, &ISystemAppletProxy::GetDisplayController, "GetDisplayController"}, + {10, nullptr, "GetProcessWindingController"}, + {11, &ISystemAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, + {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, + {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, + {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, + {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, + }; + RegisterHandlers(functions); + } + +private: + void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ICommonStateGetter>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetSelfController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISelfController>(nvflinger); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetWindowController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IWindowController>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetAudioController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IAudioController>(); + NGLOG_DEBUG(Service_AM, "called"); } + void GetDisplayController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDisplayController>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetDebugFunctions(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDebugFunctions>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ILibraryAppletCreator>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHomeMenuFunctions>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetGlobalStateController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IGlobalStateController>(); + NGLOG_DEBUG(Service_AM, "called"); + } + + void GetApplicationCreator(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IApplicationCreator>(); + NGLOG_DEBUG(Service_AM, "called"); + } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; }; +void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemAppletProxy>(nvflinger); + NGLOG_DEBUG(Service_AM, "called"); +} + +void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger); + NGLOG_DEBUG(Service_AM, "called"); +} + void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) { static const FunctionInfo functions[] = { - {100, nullptr, "OpenSystemAppletProxy"}, + {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"}, {200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"}, - {201, nullptr, "OpenLibraryAppletProxy"}, + {201, &AppletAE::OpenLibraryAppletProxy, "OpenLibraryAppletProxy"}, {300, nullptr, "OpenOverlayAppletProxy"}, {350, nullptr, "OpenSystemApplicationProxy"}, {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"}, @@ -108,5 +222,4 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) RegisterHandlers(functions); } -} // namespace AM -} // namespace Service +} // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 38fc428fb..f3a96651e 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -21,6 +21,8 @@ public: ~AppletAE() = default; private: + void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx); + void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx); void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index bdcebe689..587a922fe 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -8,8 +8,7 @@ #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/nvflinger/nvflinger.h" -namespace Service { -namespace AM { +namespace Service::AM { class IApplicationProxy final : public ServiceFramework<IApplicationProxy> { public: @@ -21,6 +20,7 @@ public: {2, &IApplicationProxy::GetWindowController, "GetWindowController"}, {3, &IApplicationProxy::GetAudioController, "GetAudioController"}, {4, &IApplicationProxy::GetDisplayController, "GetDisplayController"}, + {10, nullptr, "GetProcessWindingController"}, {11, &IApplicationProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, {20, &IApplicationProxy::GetApplicationFunctions, "GetApplicationFunctions"}, {1000, &IApplicationProxy::GetDebugFunctions, "GetDebugFunctions"}, @@ -33,56 +33,56 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISelfController>(nvflinger); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationFunctions>(); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; @@ -92,16 +92,15 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationProxy>(nvflinger); - LOG_DEBUG(Service_AM, "called"); + NGLOG_DEBUG(Service_AM, "called"); } AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)) { static const FunctionInfo functions[] = { - {0x00000000, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, + {0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, }; RegisterHandlers(functions); } -} // namespace AM -} // namespace Service +} // namespace Service::AM diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 8b55d2fcb..5b6dfb48f 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -6,8 +6,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/service/aoc/aoc_u.h" -namespace Service { -namespace AOC { +namespace Service::AOC { AOC_U::AOC_U() : ServiceFramework("aoc:u") { static const FunctionInfo functions[] = { @@ -19,6 +18,7 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u") { {5, nullptr, "GetAddOnContentBaseId"}, {6, nullptr, "PrepareAddOnContentByApplicationId"}, {7, nullptr, "PrepareAddOnContent"}, + {8, nullptr, "GetAddOnContentListChangedEvent"}, }; RegisterHandlers(functions); } @@ -27,19 +27,18 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(0); - LOG_WARNING(Service_AOC, "(STUBBED) called"); + NGLOG_WARNING(Service_AOC, "(STUBBED) called"); } void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(0); - LOG_WARNING(Service_AOC, "(STUBBED) called"); + NGLOG_WARNING(Service_AOC, "(STUBBED) called"); } void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<AOC_U>()->InstallAsService(service_manager); } -} // namespace AOC -} // namespace Service +} // namespace Service::AOC diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 6e0ba15a5..17d48ef30 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace AOC { +namespace Service::AOC { class AOC_U final : public ServiceFramework<AOC_U> { public: @@ -22,5 +21,4 @@ private: /// Registers all AOC services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace AOC -} // namespace Service +} // namespace Service::AOC diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index c4b09b435..7a185c6c8 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -7,8 +7,7 @@ #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/interface.h" -namespace Service { -namespace APM { +namespace Service::APM { void InstallInterfaces(SM::ServiceManager& service_manager) { auto module_ = std::make_shared<Module>(); @@ -16,5 +15,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager); } -} // namespace APM -} // namespace Service +} // namespace Service::APM diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index 070ab21f8..90a80d51b 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace APM { +namespace Service::APM { enum class PerformanceMode : u8 { Handheld = 0, @@ -23,5 +22,4 @@ public: /// Registers all AM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace APM -} // namespace Service +} // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index 0179351ba..3a03188ce 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -7,8 +7,7 @@ #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/interface.h" -namespace Service { -namespace APM { +namespace Service::APM { class ISession final : public ServiceFramework<ISession> { public: @@ -30,8 +29,8 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_APM, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode), - config); + NGLOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode), + config); } void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { @@ -43,7 +42,7 @@ private: rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); // Performance configuration - LOG_WARNING(Service_APM, "(STUBBED) called mode=%u", static_cast<u32>(mode)); + NGLOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode)); } }; @@ -62,5 +61,4 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<ISession>(); } -} // namespace APM -} // namespace Service +} // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h index 7d53721de..b99dbb412 100644 --- a/src/core/hle/service/apm/interface.h +++ b/src/core/hle/service/apm/interface.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace APM { +namespace Service::APM { class APM final : public ServiceFramework<APM> { public: @@ -23,5 +22,4 @@ private: /// Registers all AM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace APM -} // namespace Service +} // namespace Service::APM diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index ee749fddd..cbc49e55e 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -7,22 +7,26 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audin_u.h" -namespace Service { -namespace Audio { +namespace Service::Audio { class IAudioIn final : public ServiceFramework<IAudioIn> { public: IAudioIn() : ServiceFramework("IAudioIn") { static const FunctionInfo functions[] = { - {0x0, nullptr, "GetAudioInState"}, - {0x1, nullptr, "StartAudioIn"}, - {0x2, nullptr, "StopAudioIn"}, - {0x3, nullptr, "AppendAudioInBuffer_1"}, - {0x4, nullptr, "RegisterBufferEvent"}, - {0x5, nullptr, "GetReleasedAudioInBuffer_1"}, - {0x6, nullptr, "ContainsAudioInBuffer"}, - {0x7, nullptr, "AppendAudioInBuffer_2"}, - {0x8, nullptr, "GetReleasedAudioInBuffer_2"}, + {0, nullptr, "GetAudioInState"}, + {1, nullptr, "StartAudioIn"}, + {2, nullptr, "StopAudioIn"}, + {3, nullptr, "AppendAudioInBuffer"}, + {4, nullptr, "RegisterBufferEvent"}, + {5, nullptr, "GetReleasedAudioInBuffer"}, + {6, nullptr, "ContainsAudioInBuffer"}, + {7, nullptr, "AppendAudioInBufferWithUserEvent"}, + {8, nullptr, "AppendAudioInBufferAuto"}, + {9, nullptr, "GetReleasedAudioInBufferAuto"}, + {10, nullptr, "AppendAudioInBufferWithUserEventAuto"}, + {11, nullptr, "GetAudioInBufferCount"}, + {12, nullptr, "SetAudioInDeviceGain"}, + {13, nullptr, "GetAudioInDeviceGain"}, }; RegisterHandlers(functions); } @@ -31,11 +35,10 @@ public: AudInU::AudInU() : ServiceFramework("audin:u") { static const FunctionInfo functions[] = { - {0x00000000, nullptr, "ListAudioIns"}, - {0x00000001, nullptr, "OpenAudioIn"}, + {0, nullptr, "ListAudioIns"}, {1, nullptr, "OpenAudioIn"}, {2, nullptr, "Unknown"}, + {3, nullptr, "OpenAudioInAuto"}, {4, nullptr, "ListAudioInsAuto"}, }; RegisterHandlers(functions); } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 2b8576756..2e65efb5b 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -10,8 +10,7 @@ namespace Kernel { class HLERequestContext; } -namespace Service { -namespace Audio { +namespace Service::Audio { class AudInU final : public ServiceFramework<AudInU> { public: @@ -19,5 +18,4 @@ public: ~AudInU() = default; }; -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 3f7fb44eb..92f910b5f 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -9,8 +9,7 @@ #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/codecctl.h" -namespace Service { -namespace Audio { +namespace Service::Audio { void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<AudOutU>()->InstallAsService(service_manager); @@ -20,5 +19,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<CodecCtl>()->InstallAsService(service_manager); } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h index cbd56b2a8..95e5691f7 100644 --- a/src/core/hle/service/audio/audio.h +++ b/src/core/hle/service/audio/audio.h @@ -6,11 +6,9 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Audio { +namespace Service::Audio { /// Registers all Audio services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 780a4e6e5..402eaa306 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -10,8 +10,7 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audout_u.h" -namespace Service { -namespace Audio { +namespace Service::Audio { /// Switch sample rate frequency constexpr u32 sample_rate{48000}; @@ -19,21 +18,24 @@ constexpr u32 sample_rate{48000}; /// to more audio channels (probably when Docked I guess) constexpr u32 audio_channels{2}; /// TODO(st4rk): find a proper value for the audio_ticks -constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 500)}; +constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 500)}; class IAudioOut final : public ServiceFramework<IAudioOut> { public: IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) { static const FunctionInfo functions[] = { - {0x0, nullptr, "GetAudioOutState"}, - {0x1, &IAudioOut::StartAudioOut, "StartAudioOut"}, - {0x2, &IAudioOut::StopAudioOut, "StopAudioOut"}, - {0x3, &IAudioOut::AppendAudioOutBuffer_1, "AppendAudioOutBuffer_1"}, - {0x4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, - {0x5, &IAudioOut::GetReleasedAudioOutBuffer_1, "GetReleasedAudioOutBuffer_1"}, - {0x6, nullptr, "ContainsAudioOutBuffer"}, - {0x7, nullptr, "AppendAudioOutBuffer_2"}, - {0x8, nullptr, "GetReleasedAudioOutBuffer_2"}, + {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, + {1, &IAudioOut::StartAudioOut, "StartAudioOut"}, + {2, &IAudioOut::StopAudioOut, "StopAudioOut"}, + {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"}, + {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, + {5, &IAudioOut::GetReleasedAudioOutBuffer, "GetReleasedAudioOutBuffer"}, + {6, nullptr, "ContainsAudioOutBuffer"}, + {7, nullptr, "AppendAudioOutBufferAuto"}, + {8, nullptr, "GetReleasedAudioOutBufferAuto"}, + {9, nullptr, "GetAudioOutBufferCount"}, + {10, nullptr, "GetAudioOutPlayedSampleCount"}, + {11, nullptr, "FlushAudioOutBuffers"}, }; RegisterHandlers(functions); @@ -52,11 +54,20 @@ public: CoreTiming::ScheduleEvent(audio_ticks, audio_event); } - ~IAudioOut() = default; + ~IAudioOut() { + CoreTiming::UnscheduleEvent(audio_event, 0); + } private: + void GetAudioOutState(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(audio_out_state)); + } + void StartAudioOut(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); // Start audio audio_out_state = AudioState::Started; @@ -66,7 +77,7 @@ private: } void StopAudioOut(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); // Stop audio audio_out_state = AudioState::Stopped; @@ -78,15 +89,15 @@ private: } void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_event); } - void AppendAudioOutBuffer_1(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); IPC::RequestParser rp{ctx}; const u64 key{rp.Pop<u64>()}; @@ -96,8 +107,8 @@ private: rb.Push(RESULT_SUCCESS); } - void GetReleasedAudioOutBuffer_1(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + void GetReleasedAudioOutBuffer(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); // TODO(st4rk): This is how libtransistor currently implements the // GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address @@ -153,7 +164,7 @@ private: }; void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); IPC::RequestParser rp{ctx}; const std::string audio_interface = "AudioInterface"; @@ -169,7 +180,7 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { } void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); if (!audio_out_interface) { audio_out_interface = std::make_shared<IAudioOut>(); @@ -185,12 +196,11 @@ void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { } AudOutU::AudOutU() : ServiceFramework("audout:u") { - static const FunctionInfo functions[] = {{0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"}, - {0x00000001, &AudOutU::OpenAudioOut, "OpenAudioOut"}, - {0x00000002, nullptr, "Unknown2"}, - {0x00000003, nullptr, "Unknown3"}}; + static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOuts, "ListAudioOuts"}, + {1, &AudOutU::OpenAudioOut, "OpenAudioOut"}, + {2, nullptr, "ListAudioOutsAuto"}, + {3, nullptr, "OpenAudioOutAuto"}}; RegisterHandlers(functions); } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index 7fbce2225..1f9bb9bcf 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -10,8 +10,7 @@ namespace Kernel { class HLERequestContext; } -namespace Service { -namespace Audio { +namespace Service::Audio { class IAudioOut; @@ -37,5 +36,4 @@ private: }; }; -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp index f2626ec70..74909415c 100644 --- a/src/core/hle/service/audio/audrec_u.cpp +++ b/src/core/hle/service/audio/audrec_u.cpp @@ -7,20 +7,22 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audrec_u.h" -namespace Service { -namespace Audio { +namespace Service::Audio { class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> { public: IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") { static const FunctionInfo functions[] = { - {0x0, nullptr, "GetFinalOutputRecorderState"}, - {0x1, nullptr, "StartFinalOutputRecorder"}, - {0x2, nullptr, "StopFinalOutputRecorder"}, - {0x3, nullptr, "AppendFinalOutputRecorderBuffer"}, - {0x4, nullptr, "RegisterBufferEvent"}, - {0x5, nullptr, "GetReleasedFinalOutputRecorderBuffer"}, - {0x6, nullptr, "ContainsFinalOutputRecorderBuffer"}, + {0, nullptr, "GetFinalOutputRecorderState"}, + {1, nullptr, "StartFinalOutputRecorder"}, + {2, nullptr, "StopFinalOutputRecorder"}, + {3, nullptr, "AppendFinalOutputRecorderBuffer"}, + {4, nullptr, "RegisterBufferEvent"}, + {5, nullptr, "GetReleasedFinalOutputRecorderBuffer"}, + {6, nullptr, "ContainsFinalOutputRecorderBuffer"}, + {7, nullptr, "Unknown"}, + {8, nullptr, "AppendFinalOutputRecorderBufferAuto"}, + {9, nullptr, "GetReleasedFinalOutputRecorderBufferAuto"}, }; RegisterHandlers(functions); } @@ -29,10 +31,9 @@ public: AudRecU::AudRecU() : ServiceFramework("audrec:u") { static const FunctionInfo functions[] = { - {0x00000000, nullptr, "OpenFinalOutputRecorder"}, + {0, nullptr, "OpenFinalOutputRecorder"}, }; RegisterHandlers(functions); } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h index c31e412c1..46daa33a4 100644 --- a/src/core/hle/service/audio/audrec_u.h +++ b/src/core/hle/service/audio/audrec_u.h @@ -10,8 +10,7 @@ namespace Kernel { class HLERequestContext; } -namespace Service { -namespace Audio { +namespace Service::Audio { class AudRecU final : public ServiceFramework<AudRecU> { public: @@ -19,5 +18,4 @@ public: ~AudRecU() = default; }; -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index dda135d18..6e8002bc9 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/alignment.h" #include "common/logging/log.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" @@ -9,26 +10,27 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audren_u.h" -namespace Service { -namespace Audio { +namespace Service::Audio { /// TODO(bunnei): Find a proper value for the audio_ticks -constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 200)}; +constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 200)}; class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { public: IAudioRenderer() : ServiceFramework("IAudioRenderer") { static const FunctionInfo functions[] = { - {0x0, nullptr, "GetAudioRendererSampleRate"}, - {0x1, nullptr, "GetAudioRendererSampleCount"}, - {0x2, nullptr, "GetAudioRendererMixBufferCount"}, - {0x3, nullptr, "GetAudioRendererState"}, - {0x4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, - {0x5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, - {0x6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, - {0x7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, - {0x8, nullptr, "SetAudioRendererRenderingTimeLimit"}, - {0x9, nullptr, "GetAudioRendererRenderingTimeLimit"}, + {0, nullptr, "GetAudioRendererSampleRate"}, + {1, nullptr, "GetAudioRendererSampleCount"}, + {2, nullptr, "GetAudioRendererMixBufferCount"}, + {3, nullptr, "GetAudioRendererState"}, + {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, + {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, + {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, + {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, + {8, nullptr, "SetAudioRendererRenderingTimeLimit"}, + {9, nullptr, "GetAudioRendererRenderingTimeLimit"}, + {10, nullptr, "RequestUpdateAudioRendererAuto"}, + {11, nullptr, "ExecuteAudioRendererRendering"}, }; RegisterHandlers(functions); @@ -45,7 +47,9 @@ public: // Start the audio event CoreTiming::ScheduleEvent(audio_ticks, audio_event); } - ~IAudioRenderer() = default; + ~IAudioRenderer() { + CoreTiming::UnscheduleEvent(audio_event, 0); + } private: void UpdateAudioCallback() { @@ -53,16 +57,16 @@ private: } void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "%s", ctx.Description().c_str()); + NGLOG_DEBUG(Service_Audio, "{}", ctx.Description()); AudioRendererResponseData response_data{}; response_data.section_0_size = - response_data.state_entries.size() * sizeof(AudioRendererStateEntry); - response_data.section_1_size = response_data.section_1.size(); - response_data.section_2_size = response_data.section_2.size(); - response_data.section_3_size = response_data.section_3.size(); - response_data.section_4_size = response_data.section_4.size(); - response_data.section_5_size = response_data.section_5.size(); + static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry)); + response_data.section_1_size = static_cast<u32>(response_data.section_1.size()); + response_data.section_2_size = static_cast<u32>(response_data.section_2.size()); + response_data.section_3_size = static_cast<u32>(response_data.section_3.size()); + response_data.section_4_size = static_cast<u32>(response_data.section_4.size()); + response_data.section_5_size = static_cast<u32>(response_data.section_5.size()); response_data.total_size = sizeof(AudioRendererResponseData); for (unsigned i = 0; i < response_data.state_entries.size(); i++) { @@ -76,7 +80,7 @@ private: rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); } void StartAudioRenderer(Kernel::HLERequestContext& ctx) { @@ -84,7 +88,7 @@ private: rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); } void StopAudioRenderer(Kernel::HLERequestContext& ctx) { @@ -92,7 +96,7 @@ private: rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); } void QuerySystemEvent(Kernel::HLERequestContext& ctx) { @@ -102,7 +106,7 @@ private: rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(system_event); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); } struct AudioRendererStateEntry { @@ -149,12 +153,96 @@ private: Kernel::SharedPtr<Kernel::Event> system_event; }; +class IAudioDevice final : public ServiceFramework<IAudioDevice> { +public: + IAudioDevice() : ServiceFramework("IAudioDevice") { + static const FunctionInfo functions[] = { + {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, + {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, + {2, nullptr, "GetAudioDeviceOutputVolume"}, + {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, + {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, + {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, + {6, &IAudioDevice::ListAudioDeviceName, + "ListAudioDeviceNameAuto"}, // TODO(ogniK): Confirm if autos are identical to non auto + {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, + {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, + {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, + {11, nullptr, "QueryAudioDeviceInputEvent"}, + {12, nullptr, "QueryAudioDeviceOutputEvent"}, + }; + RegisterHandlers(functions); + + buffer_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent"); + } + +private: + void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const std::string audio_interface = "AudioInterface"; + ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size()); + + IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::RequestParser rp{ctx}; + f32 volume = static_cast<f32>(rp.Pop<u32>()); + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } + + void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const std::string audio_interface = "AudioDevice"; + ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size()); + + IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); + + buffer_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(buffer_event); + } + + void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + Kernel::SharedPtr<Kernel::Event> buffer_event; + +}; // namespace Audio + AudRenU::AudRenU() : ServiceFramework("audren:u") { static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"}, - {2, &AudRenU::GetAudioRenderersProcessMasterVolume, "GetAudioRenderersProcessMasterVolume"}, - {3, nullptr, "SetAudioRenderersProcessMasterVolume"}, + {2, &AudRenU::GetAudioDevice, "GetAudioDevice"}, + {3, nullptr, "OpenAudioRendererAuto"}, + {4, nullptr, "GetAudioDeviceServiceWithRevisionInfo"}, }; RegisterHandlers(functions); } @@ -165,25 +253,85 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<Audio::IAudioRenderer>(); - LOG_DEBUG(Service_Audio, "called"); + NGLOG_DEBUG(Service_Audio, "called"); } void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto params = rp.PopRaw<WorkerBufferParameters>(); + + u64 buffer_sz = Common::AlignUp(4 * params.unknown8, 0x40); + buffer_sz += params.unknownC * 1024; + buffer_sz += 0x940 * (params.unknownC + 1); + buffer_sz += 0x3F0 * params.voice_count; + buffer_sz += Common::AlignUp(8 * (params.unknownC + 1), 0x10); + buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10); + buffer_sz += + Common::AlignUp((0x3C0 * (params.sink_count + params.unknownC) + 4 * params.sample_count) * + (params.unknown8 + 6), + 0x40); + + if (IsFeatureSupported(AudioFeatures::Splitter, params.magic)) { + u32 count = params.unknownC + 1; + u64 node_count = Common::AlignUp(count, 0x40); + u64 node_state_buffer_sz = + 4 * (node_count * node_count) + 0xC * node_count + 2 * (node_count / 8); + u64 edge_matrix_buffer_sz = 0; + node_count = Common::AlignUp(count * count, 0x40); + if (node_count >> 31 != 0) { + edge_matrix_buffer_sz = (node_count | 7) / 8; + } else { + edge_matrix_buffer_sz = node_count / 8; + } + buffer_sz += Common::AlignUp(node_state_buffer_sz + edge_matrix_buffer_sz, 0x10); + } + + buffer_sz += 0x20 * (params.effect_count + 4 * params.voice_count) + 0x50; + if (IsFeatureSupported(AudioFeatures::Splitter, params.magic)) { + buffer_sz += 0xE0 * params.unknown2c; + buffer_sz += 0x20 * params.splitter_count; + buffer_sz += Common::AlignUp(4 * params.unknown2c, 0x10); + } + buffer_sz = Common::AlignUp(buffer_sz, 0x40) + 0x170 * params.sink_count; + u64 output_sz = buffer_sz + 0x280 * params.sink_count + 0x4B0 * params.effect_count + + ((params.voice_count * 256) | 0x40); + + if (params.unknown1c >= 1) { + output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count + + 16 * params.voice_count + 16) + + 0x658) * + (params.unknown1c + 1) + + 0xc0, + 0x40) + + output_sz; + } + output_sz = Common::AlignUp(output_sz + 0x1807e, 0x1000); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0x400); + rb.Push<u64>(output_sz); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + NGLOG_DEBUG(Service_Audio, "called, buffer_size=0x{:X}", output_sz); } -void AudRenU::GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 3}; +void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(100); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + rb.PushIpcInterface<Audio::IAudioDevice>(); + + NGLOG_DEBUG(Service_Audio, "called"); +} + +bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const { + u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap + switch (feature) { + case AudioFeatures::Splitter: + return version_num >= 2; + default: + return false; + } } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 939d353a9..fe53de4ce 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -10,8 +10,7 @@ namespace Kernel { class HLERequestContext; } -namespace Service { -namespace Audio { +namespace Service::Audio { class AudRenU final : public ServiceFramework<AudRenU> { public: @@ -21,8 +20,32 @@ public: private: void OpenAudioRenderer(Kernel::HLERequestContext& ctx); void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); - void GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx); + void GetAudioDevice(Kernel::HLERequestContext& ctx); + + struct WorkerBufferParameters { + u32_le sample_rate; + u32_le sample_count; + u32_le unknown8; + u32_le unknownC; + u32_le voice_count; + u32_le sink_count; + u32_le effect_count; + u32_le unknown1c; + u8 unknown20; + u8 padding1[3]; + u32_le splitter_count; + u32_le unknown2c; + u8 padding2[4]; + u32_le magic; + }; + static_assert(sizeof(WorkerBufferParameters) == 52, + "WorkerBufferParameters is an invalid size"); + + enum class AudioFeatures : u32 { + Splitter, + }; + + bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const; }; -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp index d2a7f4cd0..212c8d448 100644 --- a/src/core/hle/service/audio/codecctl.cpp +++ b/src/core/hle/service/audio/codecctl.cpp @@ -7,27 +7,25 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/codecctl.h" -namespace Service { -namespace Audio { +namespace Service::Audio { CodecCtl::CodecCtl() : ServiceFramework("codecctl") { static const FunctionInfo functions[] = { - {0x00000000, nullptr, "InitializeCodecController"}, - {0x00000001, nullptr, "FinalizeCodecController"}, - {0x00000002, nullptr, "SleepCodecController"}, - {0x00000003, nullptr, "WakeCodecController"}, - {0x00000004, nullptr, "SetCodecVolume"}, - {0x00000005, nullptr, "GetCodecVolumeMax"}, - {0x00000006, nullptr, "GetCodecVolumeMin"}, - {0x00000007, nullptr, "SetCodecActiveTarget"}, - {0x00000008, nullptr, "Unknown"}, - {0x00000009, nullptr, "BindCodecHeadphoneMicJackInterrupt"}, - {0x0000000A, nullptr, "IsCodecHeadphoneMicJackInserted"}, - {0x0000000B, nullptr, "ClearCodecHeadphoneMicJackInterrupt"}, - {0x0000000C, nullptr, "IsCodecDeviceRequested"}, + {0, nullptr, "InitializeCodecController"}, + {1, nullptr, "FinalizeCodecController"}, + {2, nullptr, "SleepCodecController"}, + {3, nullptr, "WakeCodecController"}, + {4, nullptr, "SetCodecVolume"}, + {5, nullptr, "GetCodecVolumeMax"}, + {6, nullptr, "GetCodecVolumeMin"}, + {7, nullptr, "SetCodecActiveTarget"}, + {8, nullptr, "GetCodecActiveTarget"}, + {9, nullptr, "BindCodecHeadphoneMicJackInterrupt"}, + {10, nullptr, "IsCodecHeadphoneMicJackInserted"}, + {11, nullptr, "ClearCodecHeadphoneMicJackInterrupt"}, + {12, nullptr, "IsCodecDeviceRequested"}, }; RegisterHandlers(functions); } -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h index 1121ab0b1..d9ac29b67 100644 --- a/src/core/hle/service/audio/codecctl.h +++ b/src/core/hle/service/audio/codecctl.h @@ -10,8 +10,7 @@ namespace Kernel { class HLERequestContext; } -namespace Service { -namespace Audio { +namespace Service::Audio { class CodecCtl final : public ServiceFramework<CodecCtl> { public: @@ -19,5 +18,4 @@ public: ~CodecCtl() = default; }; -} // namespace Audio -} // namespace Service +} // namespace Service::Audio diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp new file mode 100644 index 000000000..20ce692dc --- /dev/null +++ b/src/core/hle/service/bcat/bcat.cpp @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/bcat/bcat.h" + +namespace Service::BCAT { + +BCAT::BCAT(std::shared_ptr<Module> module, const char* name) + : Module::Interface(std::move(module), name) { + static const FunctionInfo functions[] = { + {0, &BCAT::CreateBcatService, "CreateBcatService"}, + }; + RegisterHandlers(functions); +} +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h new file mode 100644 index 000000000..6632996a0 --- /dev/null +++ b/src/core/hle/service/bcat/bcat.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/bcat/module.h" + +namespace Service::BCAT { + +class BCAT final : public Module::Interface { +public: + explicit BCAT(std::shared_ptr<Module> module, const char* name); +}; + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp new file mode 100644 index 000000000..52be9db22 --- /dev/null +++ b/src/core/hle/service/bcat/module.cpp @@ -0,0 +1,53 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/bcat/bcat.h" +#include "core/hle/service/bcat/module.h" + +namespace Service::BCAT { + +class IBcatService final : public ServiceFramework<IBcatService> { +public: + IBcatService() : ServiceFramework("IBcatService") { + static const FunctionInfo functions[] = { + {10100, nullptr, "RequestSyncDeliveryCache"}, + {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"}, + {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, + {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, + {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, + {30100, nullptr, "SetPassphrase"}, + {30200, nullptr, "RegisterBackgroundDeliveryTask"}, + {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, + {30202, nullptr, "BlockDeliveryTask"}, + {30203, nullptr, "UnblockDeliveryTask"}, + {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, + {90200, nullptr, "GetDeliveryList"}, + {90201, nullptr, "ClearDeliveryCacheStorage"}, + {90300, nullptr, "GetPushNotificationLog"}, + }; + RegisterHandlers(functions); + } +}; + +void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IBcatService>(); + NGLOG_DEBUG(Service_BCAT, "called"); +} + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); + std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager); + std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager); + std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager); +} + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h new file mode 100644 index 000000000..8366fb877 --- /dev/null +++ b/src/core/hle/service/bcat/module.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::BCAT { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void CreateBcatService(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +/// Registers all BCAT services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::BCAT diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp new file mode 100644 index 000000000..2d4282209 --- /dev/null +++ b/src/core/hle/service/fatal/fatal.cpp @@ -0,0 +1,36 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/fatal/fatal.h" +#include "core/hle/service/fatal/fatal_p.h" +#include "core/hle/service/fatal/fatal_u.h" + +namespace Service::Fatal { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + u32 error_code = rp.Pop<u32>(); + NGLOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_Fatal, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<Fatal_P>(module)->InstallAsService(service_manager); + std::make_shared<Fatal_U>(module)->InstallAsService(service_manager); +} + +} // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h new file mode 100644 index 000000000..5bd111a14 --- /dev/null +++ b/src/core/hle/service/fatal/fatal.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Fatal { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); + void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp new file mode 100644 index 000000000..a5254ac2f --- /dev/null +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -0,0 +1,12 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/fatal/fatal_p.h" + +namespace Service::Fatal { + +Fatal_P::Fatal_P(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "fatal:p") {} + +} // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h new file mode 100644 index 000000000..bfd8c8b74 --- /dev/null +++ b/src/core/hle/service/fatal/fatal_p.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/fatal/fatal.h" + +namespace Service::Fatal { + +class Fatal_P final : public Module::Interface { +public: + explicit Fatal_P(std::shared_ptr<Module> module); +}; + +} // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp new file mode 100644 index 000000000..f0631329e --- /dev/null +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/fatal/fatal_u.h" + +namespace Service::Fatal { + +Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { + static const FunctionInfo functions[] = { + {0, nullptr, "ThrowFatal"}, + {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, + {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h new file mode 100644 index 000000000..9b1a9e97a --- /dev/null +++ b/src/core/hle/service/fatal/fatal_u.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/fatal/fatal.h" + +namespace Service::Fatal { + +class Fatal_U final : public Module::Interface { +public: + explicit Fatal_U(std::shared_ptr<Module> module); +}; + +} // namespace Service::Fatal diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 4b47548fd..68d1c90a5 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -3,12 +3,14 @@ // Refer to the license.txt file included. #include <boost/container/flat_map.hpp> +#include "common/file_util.h" #include "core/file_sys/filesystem.h" +#include "core/file_sys/savedata_factory.h" +#include "core/file_sys/sdmc_factory.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_srv.h" -namespace Service { -namespace FileSystem { +namespace Service::FileSystem { /** * Map of registered file systems, identified by type. Once an file system is registered here, it @@ -23,14 +25,14 @@ ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& fact ASSERT_MSG(inserted, "Tried to register more than one system with same id code"); auto& filesystem = result.first->second; - LOG_DEBUG(Service_FS, "Registered file system %s with id code 0x%08X", - filesystem->GetName().c_str(), static_cast<u32>(type)); + NGLOG_DEBUG(Service_FS, "Registered file system {} with id code 0x{:08X}", + filesystem->GetName(), static_cast<u32>(type)); return RESULT_SUCCESS; } ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, FileSys::Path& path) { - LOG_TRACE(Service_FS, "Opening FileSystem with type=%d", type); + NGLOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast<u32>(type)); auto itr = filesystem_map.find(type); if (itr == filesystem_map.end()) { @@ -41,14 +43,35 @@ ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, return itr->second->Open(path); } -void UnregisterFileSystems() { +ResultCode FormatFileSystem(Type type) { + NGLOG_TRACE(Service_FS, "Formatting FileSystem with type={}", static_cast<u32>(type)); + + auto itr = filesystem_map.find(type); + if (itr == filesystem_map.end()) { + // TODO(bunnei): Find a better error code for this + return ResultCode(-1); + } + + FileSys::Path unused; + return itr->second->Format(unused); +} + +void RegisterFileSystems() { filesystem_map.clear(); + + std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX); + + auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory)); + RegisterFileSystem(std::move(savedata), Type::SaveData); + + auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory)); + RegisterFileSystem(std::move(sdcard), Type::SDMC); } void InstallInterfaces(SM::ServiceManager& service_manager) { - UnregisterFileSystems(); + RegisterFileSystems(); std::make_shared<FSP_SRV>()->InstallAsService(service_manager); } -} // namespace FileSystem -} // namespace Service +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index a674c9493..56d26146e 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -25,6 +25,8 @@ namespace FileSystem { /// Supported FileSystem types enum class Type { RomFS = 1, + SaveData = 2, + SDMC = 3, }; /** @@ -43,6 +45,13 @@ ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& fact ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, FileSys::Path& path); +/** + * Formats a file system + * @param type Type of the file system to format + * @return ResultCode of the operation + */ +ResultCode FormatFileSystem(Type type); + /// Registers all Filesystem services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 87a07e457..1cf97e876 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -2,8 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/directory.h" #include "core/file_sys/filesystem.h" #include "core/file_sys/storage.h" #include "core/hle/ipc_helpers.h" @@ -12,8 +15,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_srv.h" -namespace Service { -namespace FileSystem { +namespace Service::FileSystem { class IStorage final : public ServiceFramework<IStorage> { public: @@ -21,7 +23,7 @@ public: : ServiceFramework("IStorage"), backend(std::move(backend)) { static const FunctionInfo functions[] = { {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"}, - {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, + {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, }; RegisterHandlers(functions); } @@ -34,7 +36,7 @@ private: const s64 offset = rp.Pop<s64>(); const s64 length = rp.Pop<s64>(); - LOG_DEBUG(Service_FS, "called, offset=0x%llx, length=0x%llx", offset, length); + NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); // Error checking if (length < 0) { @@ -65,14 +67,421 @@ private: } }; +class IFile final : public ServiceFramework<IFile> { +public: + explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend) + : ServiceFramework("IFile"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, + {2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"}, + {4, &IFile::GetSize, "GetSize"}, {5, nullptr, "OperateRange"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::StorageBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + const s64 offset = rp.Pop<s64>(); + const s64 length = rp.Pop<s64>(); + + NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); + + // Error checking + if (length < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); + return; + } + if (offset < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); + return; + } + + // Read the data from the Storage backend + std::vector<u8> output(length); + ResultVal<size_t> res = backend->Read(offset, length, output.data()); + if (res.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(*res)); + } + + void Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + const s64 offset = rp.Pop<s64>(); + const s64 length = rp.Pop<s64>(); + + NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); + + // Error checking + if (length < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); + return; + } + if (offset < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); + return; + } + + // Write the data to the Storage backend + std::vector<u8> data = ctx.ReadBuffer(); + ResultVal<size_t> res = backend->Write(offset, length, true, data.data()); + if (res.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Flush(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_FS, "called"); + backend->Flush(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 size = rp.Pop<u64>(); + backend->SetSize(size); + NGLOG_DEBUG(Service_FS, "called, size={}", size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + NGLOG_DEBUG(Service_FS, "called, size={}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(size); + } +}; + +class IDirectory final : public ServiceFramework<IDirectory> { +public: + explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend) + : ServiceFramework("IDirectory"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IDirectory::Read, "Read"}, + {1, &IDirectory::GetEntryCount, "GetEntryCount"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::DirectoryBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + + NGLOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk); + + // Calculate how many entries we can fit in the output buffer + u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); + + // Read the data from the Directory backend + std::vector<FileSys::Entry> entries(count_entries); + u64 read_entries = backend->Read(count_entries, entries.data()); + + // Convert the data into a byte array + std::vector<u8> output(entries.size() * sizeof(FileSys::Entry)); + std::memcpy(output.data(), entries.data(), output.size()); + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(read_entries); + } + + void GetEntryCount(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_FS, "called"); + + u64 count = backend->GetEntryCount(); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(count); + } +}; + +class IFileSystem final : public ServiceFramework<IFileSystem> { +public: + explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend) + : ServiceFramework("IFileSystem"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IFileSystem::CreateFile, "CreateFile"}, + {1, &IFileSystem::DeleteFile, "DeleteFile"}, + {2, &IFileSystem::CreateDirectory, "CreateDirectory"}, + {3, nullptr, "DeleteDirectory"}, + {4, nullptr, "DeleteDirectoryRecursively"}, + {5, &IFileSystem::RenameFile, "RenameFile"}, + {6, nullptr, "RenameDirectory"}, + {7, &IFileSystem::GetEntryType, "GetEntryType"}, + {8, &IFileSystem::OpenFile, "OpenFile"}, + {9, &IFileSystem::OpenDirectory, "OpenDirectory"}, + {10, &IFileSystem::Commit, "Commit"}, + {11, nullptr, "GetFreeSpaceSize"}, + {12, nullptr, "GetTotalSpaceSize"}, + {13, nullptr, "CleanDirectoryRecursively"}, + {14, nullptr, "GetFileTimeStampRaw"}, + {15, nullptr, "QueryEntry"}, + }; + RegisterHandlers(functions); + } + + void CreateFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + u64 mode = rp.Pop<u64>(); + u32 size = rp.Pop<u32>(); + + NGLOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->CreateFile(name, size)); + } + + void DeleteFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + NGLOG_DEBUG(Service_FS, "called file {}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->DeleteFile(name)); + } + + void CreateDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + NGLOG_DEBUG(Service_FS, "called directory {}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->CreateDirectory(name)); + } + + void RenameFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + std::vector<u8> buffer; + buffer.resize(ctx.BufferDescriptorX()[0].Size()); + Memory::ReadBlock(ctx.BufferDescriptorX()[0].Address(), buffer.data(), buffer.size()); + std::string src_name = Common::StringFromBuffer(buffer); + + buffer.resize(ctx.BufferDescriptorX()[1].Size()); + Memory::ReadBlock(ctx.BufferDescriptorX()[1].Address(), buffer.data(), buffer.size()); + std::string dst_name = Common::StringFromBuffer(buffer); + + NGLOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->RenameFile(src_name, dst_name)); + } + + void OpenFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>()); + + NGLOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode)); + + auto result = backend->OpenFile(name, mode); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + auto file = std::move(result.Unwrap()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFile>(std::move(file)); + } + + void OpenDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + // TODO(Subv): Implement this filter. + u32 filter_flags = rp.Pop<u32>(); + + NGLOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags); + + auto result = backend->OpenDirectory(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + auto directory = std::move(result.Unwrap()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDirectory>(std::move(directory)); + } + + void GetEntryType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + std::string name = Common::StringFromBuffer(file_buffer); + + NGLOG_DEBUG(Service_FS, "called file {}", name); + + auto result = backend->GetEntryType(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(*result)); + } + + void Commit(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + +private: + std::unique_ptr<FileSys::FileSystemBackend> backend; +}; + FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { static const FunctionInfo functions[] = { - {1, &FSP_SRV::Initalize, "Initalize"}, + {0, nullptr, "MountContent"}, + {1, &FSP_SRV::Initialize, "Initialize"}, + {2, nullptr, "OpenDataFileSystemByCurrentProcess"}, + {7, nullptr, "OpenFileSystemWithPatch"}, + {8, nullptr, "OpenFileSystemWithId"}, + {9, nullptr, "OpenDataFileSystemByApplicationId"}, + {11, nullptr, "OpenBisFileSystem"}, + {12, nullptr, "OpenBisStorage"}, + {13, nullptr, "InvalidateBisCache"}, + {17, nullptr, "OpenHostFileSystem"}, {18, &FSP_SRV::MountSdCard, "MountSdCard"}, + {19, nullptr, "FormatSdCardFileSystem"}, + {21, nullptr, "DeleteSaveDataFileSystem"}, + {22, &FSP_SRV::CreateSaveData, "CreateSaveData"}, + {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"}, + {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, + {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, + {26, nullptr, "FormatSdCardDryRun"}, + {27, nullptr, "IsExFatSupported"}, + {28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"}, + {30, nullptr, "OpenGameCardStorage"}, + {31, nullptr, "OpenGameCardFileSystem"}, + {32, nullptr, "ExtendSaveDataFileSystem"}, + {33, nullptr, "DeleteCacheStorage"}, + {34, nullptr, "GetCacheStorageSize"}, + {51, &FSP_SRV::MountSaveData, "MountSaveData"}, + {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, + {53, nullptr, "OpenReadOnlySaveDataFileSystem"}, + {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, + {58, nullptr, "ReadSaveDataFileSystemExtraData"}, + {59, nullptr, "WriteSaveDataFileSystemExtraData"}, + {60, nullptr, "OpenSaveDataInfoReader"}, + {61, nullptr, "OpenSaveDataInfoReaderBySaveDataSpaceId"}, + {62, nullptr, "OpenCacheStorageList"}, + {64, nullptr, "OpenSaveDataInternalStorageFileSystem"}, + {65, nullptr, "UpdateSaveDataMacForDebug"}, + {66, nullptr, "WriteSaveDataFileSystemExtraData2"}, + {80, nullptr, "OpenSaveDataMetaFile"}, + {81, nullptr, "OpenSaveDataTransferManager"}, + {82, nullptr, "OpenSaveDataTransferManagerVersion2"}, + {100, nullptr, "OpenImageDirectoryFileSystem"}, + {110, nullptr, "OpenContentStorageFileSystem"}, {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, + {201, nullptr, "OpenDataStorageByProgramId"}, {202, nullptr, "OpenDataStorageByDataId"}, {203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"}, + {400, nullptr, "OpenDeviceOperator"}, + {500, nullptr, "OpenSdCardDetectionEventNotifier"}, + {501, nullptr, "OpenGameCardDetectionEventNotifier"}, + {510, nullptr, "OpenSystemDataUpdateEventNotifier"}, + {511, nullptr, "NotifySystemDataUpdateEvent"}, + {600, nullptr, "SetCurrentPosixTime"}, + {601, nullptr, "QuerySaveDataTotalSize"}, + {602, nullptr, "VerifySaveDataFileSystem"}, + {603, nullptr, "CorruptSaveDataFileSystem"}, + {604, nullptr, "CreatePaddingFile"}, + {605, nullptr, "DeleteAllPaddingFiles"}, + {606, nullptr, "GetRightsId"}, + {607, nullptr, "RegisterExternalKey"}, + {608, nullptr, "UnregisterAllExternalKey"}, + {609, nullptr, "GetRightsIdByPath"}, + {610, nullptr, "GetRightsIdAndKeyGenerationByPath"}, + {611, nullptr, "SetCurrentPosixTimeWithTimeDifference"}, + {612, nullptr, "GetFreeSpaceSizeForSaveData"}, + {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"}, + {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"}, + {615, nullptr, "QuerySaveDataInternalStorageTotalSize"}, + {620, nullptr, "SetSdCardEncryptionSeed"}, + {630, nullptr, "SetSdCardAccessibility"}, + {631, nullptr, "IsSdCardAccessible"}, + {640, nullptr, "IsSignedSystemPartitionOnSdCardValid"}, + {700, nullptr, "OpenAccessFailureResolver"}, + {701, nullptr, "GetAccessFailureDetectionEvent"}, + {702, nullptr, "IsAccessFailureDetected"}, + {710, nullptr, "ResolveAccessFailure"}, + {720, nullptr, "AbandonAccessFailure"}, + {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"}, + {1000, nullptr, "SetBisRootForHost"}, + {1001, nullptr, "SetSaveDataSize"}, + {1002, nullptr, "SetSaveDataRootPath"}, + {1003, nullptr, "DisableAutoSaveDataCreation"}, + {1004, nullptr, "SetGlobalAccessLogMode"}, {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"}, + {1006, nullptr, "OutputAccessLogToSdCard"}, + {1007, nullptr, "RegisterUpdatePartition"}, + {1008, nullptr, "OpenRegisteredUpdatePartition"}, + {1009, nullptr, "GetAndClearMemoryReportInfo"}, + {1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"}, }; RegisterHandlers(functions); } @@ -88,22 +497,50 @@ void FSP_SRV::TryLoadRomFS() { } } -void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); +void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + NGLOG_DEBUG(Service_FS, "called"); + + FileSys::Path unused; + auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); +} + +void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto save_struct = rp.PopRaw<std::array<u8, 0x40>>(); + auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); + u128 uid = rp.PopRaw<u128>(); + + NGLOG_WARNING(Service_FS, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } +void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_FS, "(STUBBED) called"); + + FileSys::Path unused; + auto filesystem = OpenFileSystem(Type::SaveData, unused).Unwrap(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); +} + void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + NGLOG_WARNING(Service_FS, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -111,12 +548,12 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { } void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_FS, "called"); + NGLOG_DEBUG(Service_FS, "called"); TryLoadRomFS(); if (!romfs) { // TODO (bunnei): Find the right error code to use here - LOG_CRITICAL(Service_FS, "no file system interface available!"); + NGLOG_CRITICAL(Service_FS, "no file system interface available!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultCode(-1)); return; @@ -125,7 +562,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { // Attempt to open a StorageBackend interface to the RomFS auto storage = romfs->OpenFile({}, {}); if (storage.Failed()) { - LOG_CRITICAL(Service_FS, "no storage interface available!"); + NGLOG_CRITICAL(Service_FS, "no storage interface available!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(storage.Code()); return; @@ -137,9 +574,8 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { } void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess"); + NGLOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess"); OpenDataStorageByCurrentProcess(ctx); } -} // namespace FileSystem -} // namespace Service +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 56afc4b90..acb78fac1 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -11,8 +11,7 @@ namespace FileSys { class FileSystemBackend; } -namespace Service { -namespace FileSystem { +namespace Service::FileSystem { class FSP_SRV final : public ServiceFramework<FSP_SRV> { public: @@ -22,8 +21,10 @@ public: private: void TryLoadRomFS(); - void Initalize(Kernel::HLERequestContext& ctx); + void Initialize(Kernel::HLERequestContext& ctx); void MountSdCard(Kernel::HLERequestContext& ctx); + void CreateSaveData(Kernel::HLERequestContext& ctx); + void MountSaveData(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenRomStorage(Kernel::HLERequestContext& ctx); @@ -31,5 +32,4 @@ private: std::unique_ptr<FileSys::FileSystemBackend> romfs; }; -} // namespace FileSystem -} // namespace Service +} // namespace Service::FileSystem diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 26593bb0c..94d9fbf25 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -6,14 +6,14 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/service/friend/friend.h" #include "core/hle/service/friend/friend_a.h" +#include "core/hle/service/friend/friend_u.h" -namespace Service { -namespace Friend { +namespace Service::Friend { -void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) { +void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_Friend, "(STUBBED) called"); + NGLOG_WARNING(Service_Friend, "(STUBBED) called"); } Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) @@ -22,7 +22,7 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); std::make_shared<Friend_A>(module)->InstallAsService(service_manager); + std::make_shared<Friend_U>(module)->InstallAsService(service_manager); } -} // namespace Friend -} // namespace Service +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index ffa498397..4b72115c0 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Friend { +namespace Service::Friend { class Module final { public: @@ -15,7 +14,7 @@ public: public: Interface(std::shared_ptr<Module> module, const char* name); - void Unknown(Kernel::HLERequestContext& ctx); + void CreateFriendService(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; @@ -25,5 +24,4 @@ public: /// Registers all Friend services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace Friend -} // namespace Service +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend_a.cpp b/src/core/hle/service/friend/friend_a.cpp index e1f2397c2..a2cc81926 100644 --- a/src/core/hle/service/friend/friend_a.cpp +++ b/src/core/hle/service/friend/friend_a.cpp @@ -4,16 +4,15 @@ #include "core/hle/service/friend/friend_a.h" -namespace Service { -namespace Friend { +namespace Service::Friend { Friend_A::Friend_A(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "friend:a") { static const FunctionInfo functions[] = { - {0, &Friend_A::Unknown, "Unknown"}, + {0, &Friend_A::CreateFriendService, "CreateFriendService"}, + {1, nullptr, "CreateNotificationService"}, }; RegisterHandlers(functions); } -} // namespace Friend -} // namespace Service +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend_a.h b/src/core/hle/service/friend/friend_a.h index 68fa58297..81257583b 100644 --- a/src/core/hle/service/friend/friend_a.h +++ b/src/core/hle/service/friend/friend_a.h @@ -6,13 +6,11 @@ #include "core/hle/service/friend/friend.h" -namespace Service { -namespace Friend { +namespace Service::Friend { class Friend_A final : public Module::Interface { public: explicit Friend_A(std::shared_ptr<Module> module); }; -} // namespace Friend -} // namespace Service +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend_u.cpp b/src/core/hle/service/friend/friend_u.cpp new file mode 100644 index 000000000..90b30883f --- /dev/null +++ b/src/core/hle/service/friend/friend_u.cpp @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/friend/friend_u.h" + +namespace Service::Friend { + +Friend_U::Friend_U(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "friend:u") { + static const FunctionInfo functions[] = { + {0, &Friend_U::CreateFriendService, "CreateFriendService"}, + {1, nullptr, "CreateNotificationService"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend_u.h b/src/core/hle/service/friend/friend_u.h new file mode 100644 index 000000000..0d953d807 --- /dev/null +++ b/src/core/hle/service/friend/friend_u.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/friend/friend.h" + +namespace Service::Friend { + +class Friend_U final : public Module::Interface { +public: + explicit Friend_U(std::shared_ptr<Module> module); +}; + +} // namespace Service::Friend diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index dacd1862d..85ca4bf06 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -14,14 +14,13 @@ #include "core/hle/service/hid/hid.h" #include "core/hle/service/service.h" -namespace Service { -namespace HID { +namespace Service::HID { // Updating period for each HID device. // TODO(shinyquagsire23): These need better values. -constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 10000; -constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 10000; -constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 10000; +constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000; +constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000; +constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000; class IAppletResource final : public ServiceFramework<IAppletResource> { public: @@ -45,29 +44,37 @@ public: CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } + ~IAppletResource() { + CoreTiming::UnscheduleEvent(pad_update_event, 0); + } + private: void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(shared_mem); - LOG_DEBUG(Service_HID, "called"); + NGLOG_DEBUG(Service_HID, "called"); } void LoadInputDevices() { std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); - // TODO(shinyquagsire23): sticks, gyro, touch, mouse, keyboard + std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END, + sticks.begin(), Input::CreateDevice<Input::AnalogDevice>); + // TODO(shinyquagsire23): gyro, touch, mouse, keyboard } void UpdatePadCallback(u64 userdata, int cycles_late) { - SharedMemory* mem = reinterpret_cast<SharedMemory*>(shared_mem->GetPointer()); + SharedMemory mem{}; + std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory)); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); // Set up controllers as neon red+blue Joy-Con attached to console - ControllerHeader& controller_header = mem->controllers[Controller_Handheld].header; + ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header; controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair; controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent; controller_header.right_color_body = JOYCON_BODY_NEON_RED; @@ -75,67 +82,157 @@ private: controller_header.left_color_body = JOYCON_BODY_NEON_BLUE; controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE; - for (int layoutIdx = 0; layoutIdx < HID_NUM_LAYOUTS; layoutIdx++) { - ControllerLayout& layout = mem->controllers[Controller_Handheld].layouts[layoutIdx]; - layout.header.num_entries = HID_NUM_ENTRIES; - layout.header.max_entry_index = HID_NUM_ENTRIES - 1; - - // HID shared memory stores the state of the past 17 samples in a circlular buffer, - // each with a timestamp in number of samples since boot. - layout.header.timestamp_ticks = CoreTiming::GetTicks(); - layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES; - - ControllerInputEntry& entry = layout.entries[layout.header.latest_entry]; - entry.connection_state = ConnectionState_Connected | ConnectionState_Wired; - entry.timestamp++; - entry.timestamp_2++; // TODO(shinyquagsire23): Is this always identical to timestamp? - - // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future? - // For now everything is just the default handheld layout, but split Joy-Con will - // rotate the face buttons and directions for certain layouts. - ControllerPadState& state = entry.buttons; - using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); - - state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); - state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); - - // TODO(shinyquagsire23): Analog stick vals - - // TODO(shinyquagsire23): Update pad info proper, (circular buffers, timestamps, - // layouts) + for (size_t controller = 0; controller < mem.controllers.size(); controller++) { + for (int index = 0; index < HID_NUM_LAYOUTS; index++) { + ControllerLayout& layout = mem.controllers[controller].layouts[index]; + layout.header.num_entries = HID_NUM_ENTRIES; + layout.header.max_entry_index = HID_NUM_ENTRIES - 1; + + // HID shared memory stores the state of the past 17 samples in a circlular buffer, + // each with a timestamp in number of samples since boot. + layout.header.timestamp_ticks = CoreTiming::GetTicks(); + layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES; + + ControllerInputEntry& entry = layout.entries[layout.header.latest_entry]; + entry.timestamp++; + // TODO(shinyquagsire23): Is this always identical to timestamp? + entry.timestamp_2++; + + // TODO(shinyquagsire23): More than just handheld input + if (controller != Controller_Handheld) + continue; + + entry.connection_state = ConnectionState_Connected | ConnectionState_Wired; + + // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future? + // For now everything is just the default handheld layout, but split Joy-Con will + // rotate the face buttons and directions for certain layouts. + ControllerPadState& state = entry.buttons; + using namespace Settings::NativeButton; + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); + state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); + state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); + state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); + state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); + + state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); + state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); + state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); + state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus(); + entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + } + } + + // TODO(bunnei): Properly implement the touch screen, the below will just write empty data + + TouchScreen& touchscreen = mem.touchscreen; + const u64 last_entry = touchscreen.header.latest_entry; + const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size(); + const u64 timestamp = CoreTiming::GetTicks(); + const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1; + touchscreen.header.timestamp_ticks = timestamp; + touchscreen.header.num_entries = touchscreen.entries.size(); + touchscreen.header.latest_entry = curr_entry; + touchscreen.header.max_entry_index = touchscreen.entries.size(); + touchscreen.header.timestamp = timestamp; + touchscreen.entries[curr_entry].header.timestamp = sample_counter; + touchscreen.entries[curr_entry].header.num_touches = 0; + + // TODO(shinyquagsire23): Properly implement mouse + Mouse& mouse = mem.mouse; + const u64 last_mouse_entry = mouse.header.latest_entry; + const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size(); + const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1; + mouse.header.timestamp_ticks = timestamp; + mouse.header.num_entries = mouse.entries.size(); + mouse.header.max_entry_index = mouse.entries.size(); + mouse.header.latest_entry = curr_mouse_entry; + + mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter; + mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter; + + // TODO(shinyquagsire23): Properly implement keyboard + Keyboard& keyboard = mem.keyboard; + const u64 last_keyboard_entry = keyboard.header.latest_entry; + const u64 curr_keyboard_entry = + (keyboard.header.latest_entry + 1) % keyboard.entries.size(); + const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1; + keyboard.header.timestamp_ticks = timestamp; + keyboard.header.num_entries = keyboard.entries.size(); + keyboard.header.latest_entry = last_keyboard_entry; + keyboard.header.max_entry_index = keyboard.entries.size(); + + keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter; + keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter; + + // TODO(shinyquagsire23): Figure out what any of these are + for (size_t i = 0; i < mem.unk_input_1.size(); i++) { + UnkInput1& input = mem.unk_input_1[i]; + const u64 last_input_entry = input.header.latest_entry; + const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size(); + const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1; + + input.header.timestamp_ticks = timestamp; + input.header.num_entries = input.entries.size(); + input.header.latest_entry = last_input_entry; + input.header.max_entry_index = input.entries.size(); + + input.entries[curr_input_entry].timestamp = input_sample_counter; + input.entries[curr_input_entry].timestamp_2 = input_sample_counter; } - // TODO(shinyquagsire23): Update touch info + for (size_t i = 0; i < mem.unk_input_2.size(); i++) { + UnkInput2& input = mem.unk_input_2[i]; + + input.header.timestamp_ticks = timestamp; + input.header.num_entries = 17; + input.header.latest_entry = 0; + input.header.max_entry_index = 0; + } + + UnkInput3& input = mem.unk_input_3; + const u64 last_input_entry = input.header.latest_entry; + const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size(); + const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1; + + input.header.timestamp_ticks = timestamp; + input.header.num_entries = input.entries.size(); + input.header.latest_entry = last_input_entry; + input.header.max_entry_index = input.entries.size(); + + input.entries[curr_input_entry].timestamp = input_sample_counter; + input.entries[curr_input_entry].timestamp_2 = input_sample_counter; // TODO(shinyquagsire23): Signal events + std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory)); + // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } @@ -150,6 +247,7 @@ private: std::atomic<bool> is_device_reload_pending{true}; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> buttons; + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; }; class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { @@ -165,7 +263,7 @@ private: void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } }; @@ -178,24 +276,105 @@ public: {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"}, {21, &Hid::ActivateMouse, "ActivateMouse"}, {31, &Hid::ActivateKeyboard, "ActivateKeyboard"}, + {40, nullptr, "AcquireXpadIdEventHandle"}, + {41, nullptr, "ReleaseXpadIdEventHandle"}, + {51, nullptr, "ActivateXpad"}, + {55, nullptr, "GetXpadIds"}, + {56, nullptr, "ActivateJoyXpad"}, + {58, nullptr, "GetJoyXpadLifoHandle"}, + {59, nullptr, "GetJoyXpadIds"}, + {60, nullptr, "ActivateSixAxisSensor"}, + {61, nullptr, "DeactivateSixAxisSensor"}, + {62, nullptr, "GetSixAxisSensorLifoHandle"}, + {63, nullptr, "ActivateJoySixAxisSensor"}, + {64, nullptr, "DeactivateJoySixAxisSensor"}, + {65, nullptr, "GetJoySixAxisSensorLifoHandle"}, {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, + {67, nullptr, "StopSixAxisSensor"}, + {68, nullptr, "IsSixAxisSensorFusionEnabled"}, + {69, nullptr, "EnableSixAxisSensorFusion"}, + {70, nullptr, "SetSixAxisSensorFusionParameters"}, + {71, nullptr, "GetSixAxisSensorFusionParameters"}, + {72, nullptr, "ResetSixAxisSensorFusionParameters"}, + {73, nullptr, "SetAccelerometerParameters"}, + {74, nullptr, "GetAccelerometerParameters"}, + {75, nullptr, "ResetAccelerometerParameters"}, + {76, nullptr, "SetAccelerometerPlayMode"}, + {77, nullptr, "GetAccelerometerPlayMode"}, + {78, nullptr, "ResetAccelerometerPlayMode"}, {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"}, + {80, nullptr, "GetGyroscopeZeroDriftMode"}, + {81, nullptr, "ResetGyroscopeZeroDriftMode"}, + {82, nullptr, "IsSixAxisSensorAtRest"}, + {91, nullptr, "ActivateGesture"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, + {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, {103, &Hid::ActivateNpad, "ActivateNpad"}, + {104, nullptr, "DeactivateNpad"}, {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, + {107, nullptr, "DisconnectNpad"}, + {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"}, {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"}, - {124, nullptr, "SetNpadJoyAssignmentModeDual"}, + {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"}, + {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, + {125, nullptr, "MergeSingleJoyAsDualJoy"}, + {126, nullptr, "StartLrAssignmentMode"}, + {127, nullptr, "StopLrAssignmentMode"}, {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, + {129, nullptr, "GetNpadHandheldActivationMode"}, + {130, nullptr, "SwapNpadAssignment"}, + {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, + {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, + {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"}, {201, &Hid::SendVibrationValue, "SendVibrationValue"}, {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, + {204, nullptr, "PermitVibration"}, + {205, nullptr, "IsVibrationPermitted"}, {206, &Hid::SendVibrationValues, "SendVibrationValues"}, + {207, nullptr, "SendVibrationGcErmCommand"}, + {208, nullptr, "GetActualVibrationGcErmCommand"}, + {209, nullptr, "BeginPermitVibrationSession"}, + {210, nullptr, "EndPermitVibrationSession"}, + {300, nullptr, "ActivateConsoleSixAxisSensor"}, + {301, nullptr, "StartConsoleSixAxisSensor"}, + {302, nullptr, "StopConsoleSixAxisSensor"}, + {303, nullptr, "ActivateSevenSixAxisSensor"}, + {304, nullptr, "StartSevenSixAxisSensor"}, + {305, nullptr, "StopSevenSixAxisSensor"}, + {306, nullptr, "InitializeSevenSixAxisSensor"}, + {307, nullptr, "FinalizeSevenSixAxisSensor"}, + {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, + {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, + {400, nullptr, "IsUsbFullKeyControllerEnabled"}, + {401, nullptr, "EnableUsbFullKeyController"}, + {402, nullptr, "IsUsbFullKeyControllerConnected"}, + {403, nullptr, "HasBattery"}, + {404, nullptr, "HasLeftRightBattery"}, + {405, nullptr, "GetNpadInterfaceType"}, + {406, nullptr, "GetNpadLeftRightInterfaceType"}, + {500, nullptr, "GetPalmaConnectionHandle"}, + {501, nullptr, "InitializePalma"}, + {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, + {503, nullptr, "GetPalmaOperationInfo"}, + {504, nullptr, "PlayPalmaActivity"}, + {505, nullptr, "SetPalmaFrModeType"}, + {506, nullptr, "ReadPalmaStep"}, + {507, nullptr, "EnablePalmaStep"}, + {508, nullptr, "SuspendPalmaStep"}, + {509, nullptr, "ResetPalmaStep"}, + {510, nullptr, "ReadPalmaApplicationSection"}, + {511, nullptr, "WritePalmaApplicationSection"}, + {512, nullptr, "ReadPalmaUniqueCode"}, + {513, nullptr, "SetPalmaUniqueCodeInvalid"}, + {1000, nullptr, "SetNpadCommunicationMode"}, + {1001, nullptr, "GetNpadCommunicationMode"}, }; RegisterHandlers(functions); @@ -216,125 +395,144 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAppletResource>(applet_resource); - LOG_DEBUG(Service_HID, "called"); + NGLOG_DEBUG(Service_HID, "called"); } void ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void ActivateTouchScreen(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void ActivateMouse(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void ActivateKeyboard(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void ActivateNpad(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(event); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(joy_hold_type); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SendVibrationValue(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(0); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IActiveVibrationDeviceList>(); - LOG_DEBUG(Service_HID, "called"); + NGLOG_DEBUG(Service_HID, "called"); } void SendVibrationValues(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + NGLOG_WARNING(Service_HID, "(STUBBED) called"); } }; @@ -344,5 +542,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<Hid>()->InstallAsService(service_manager); } -} // namespace HID -} // namespace Service +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 3de9adb4b..b499308d6 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -7,8 +7,7 @@ #include "core/hle/service/service.h" #include "core/settings.h" -namespace Service { -namespace HID { +namespace Service::HID { // Begin enums and output structs @@ -49,6 +48,11 @@ enum ControllerConnectionState { ConnectionState_Wired = 1 << 1, }; +enum ControllerJoystick { + Joystick_Left = 0, + Joystick_Right = 1, +}; + enum ControllerID { Controller_Player1 = 0, Controller_Player2 = 1, @@ -64,6 +68,34 @@ enum ControllerID { // End enums and output structs +// Begin UnkInput3 + +struct UnkInput3Header { + u64 timestamp_ticks; + u64 num_entries; + u64 latest_entry; + u64 max_entry_index; +}; +static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size"); + +struct UnkInput3Entry { + u64 timestamp; + u64 timestamp_2; + u64 unk_8; + u64 unk_10; + u64 unk_18; +}; +static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size"); + +struct UnkInput3 { + UnkInput3Header header; + std::array<UnkInput3Entry, 17> entries; + std::array<u8, 0x138> padding; +}; +static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size"); + +// End UnkInput3 + // Begin TouchScreen struct TouchScreenHeader { @@ -205,6 +237,52 @@ static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect s // End Keyboard +// Begin UnkInput1 + +struct UnkInput1Header { + u64 timestamp_ticks; + u64 num_entries; + u64 latest_entry; + u64 max_entry_index; +}; +static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size"); + +struct UnkInput1Entry { + u64 timestamp; + u64 timestamp_2; + u64 unk_8; + u64 unk_10; + u64 unk_18; +}; +static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size"); + +struct UnkInput1 { + UnkInput1Header header; + std::array<UnkInput1Entry, 17> entries; + std::array<u8, 0x138> padding; +}; +static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size"); + +// End UnkInput1 + +// Begin UnkInput2 + +struct UnkInput2Header { + u64 timestamp_ticks; + u64 num_entries; + u64 latest_entry; + u64 max_entry_index; +}; +static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size"); + +struct UnkInput2 { + UnkInput2Header header; + std::array<u8, 0x1E0> padding; +}; +static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size"); + +// End UnkInput2 + // Begin Controller struct ControllerMAC { @@ -284,10 +362,10 @@ struct ControllerInputEntry { u64 timestamp; u64 timestamp_2; ControllerPadState buttons; - u32 joystick_left_x; - u32 joystick_left_y; - u32 joystick_right_x; - u32 joystick_right_y; + s32 joystick_left_x; + s32 joystick_left_y; + s32 joystick_right_x; + s32 joystick_right_y; u64 connection_state; }; static_assert(sizeof(ControllerInputEntry) == 0x30, @@ -313,17 +391,12 @@ static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorr // End Controller struct SharedMemory { - std::array<u8, 0x400> header; + UnkInput3 unk_input_3; TouchScreen touchscreen; Mouse mouse; Keyboard keyboard; - std::array<u8, 0x400> unk_section_1; - std::array<u8, 0x400> unk_section_2; - std::array<u8, 0x400> unk_section_3; - std::array<u8, 0x400> unk_section_4; - std::array<u8, 0x200> unk_section_5; - std::array<u8, 0x200> unk_section_6; - std::array<u8, 0x200> unk_section_7; + std::array<UnkInput1, 4> unk_input_1; + std::array<UnkInput2, 3> unk_input_2; std::array<u8, 0x800> unk_section_8; std::array<u8, 0x4000> controller_serials; std::array<Controller, 10> controllers; @@ -337,5 +410,4 @@ void ReloadInputDevices(); /// Registers all HID services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace HID -} // namespace Service +} // namespace Service::HID diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index b8e53d2c7..46194643e 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -9,8 +9,7 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/service/lm/lm.h" -namespace Service { -namespace LM { +namespace Service::LM { class Logger final : public ServiceFramework<Logger> { public: @@ -142,19 +141,19 @@ private: if (header.IsTailLog()) { switch (header.severity) { case MessageHeader::Severity::Trace: - LOG_TRACE(Debug_Emulated, "%s", log_stream.str().c_str()); + NGLOG_TRACE(Debug_Emulated, "{}", log_stream.str()); break; case MessageHeader::Severity::Info: - LOG_INFO(Debug_Emulated, "%s", log_stream.str().c_str()); + NGLOG_INFO(Debug_Emulated, "{}", log_stream.str()); break; case MessageHeader::Severity::Warning: - LOG_WARNING(Debug_Emulated, "%s", log_stream.str().c_str()); + NGLOG_WARNING(Debug_Emulated, "{}", log_stream.str()); break; case MessageHeader::Severity::Error: - LOG_ERROR(Debug_Emulated, "%s", log_stream.str().c_str()); + NGLOG_ERROR(Debug_Emulated, "{}", log_stream.str()); break; case MessageHeader::Severity::Critical: - LOG_CRITICAL(Debug_Emulated, "%s", log_stream.str().c_str()); + NGLOG_CRITICAL(Debug_Emulated, "{}", log_stream.str()); break; } } @@ -179,7 +178,7 @@ void LM::Initialize(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<Logger>(); - LOG_DEBUG(Service_LM, "called"); + NGLOG_DEBUG(Service_LM, "called"); } LM::LM() : ServiceFramework("lm") { @@ -189,5 +188,4 @@ LM::LM() : ServiceFramework("lm") { RegisterHandlers(functions); } -} // namespace LM -} // namespace Service +} // namespace Service::LM diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h index 371135057..63d6506fe 100644 --- a/src/core/hle/service/lm/lm.h +++ b/src/core/hle/service/lm/lm.h @@ -8,8 +8,7 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/service/service.h" -namespace Service { -namespace LM { +namespace Service::LM { class LM final : public ServiceFramework<LM> { public: @@ -23,5 +22,4 @@ private: /// Registers all LM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace LM -} // namespace Service +} // namespace Service::LM diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp new file mode 100644 index 000000000..b3a85b818 --- /dev/null +++ b/src/core/hle/service/mm/mm_u.cpp @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/service/mm/mm_u.h" + +namespace Service::MM { + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<MM_U>()->InstallAsService(service_manager); +} + +void MM_U::Initialize(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + min = rp.Pop<u32>(); + max = rp.Pop<u32>(); + current = min; + + NGLOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void MM_U::Get(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(current); +} + +MM_U::MM_U() : ServiceFramework("mm:u") { + static const FunctionInfo functions[] = { + {0, nullptr, "InitializeOld"}, {1, nullptr, "FinalizeOld"}, + {2, nullptr, "SetAndWaitOld"}, {3, nullptr, "GetOld"}, + {4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"}, + {6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::MM diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h new file mode 100644 index 000000000..79eeedf9c --- /dev/null +++ b/src/core/hle/service/mm/mm_u.h @@ -0,0 +1,29 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::MM { + +class MM_U final : public ServiceFramework<MM_U> { +public: + MM_U(); + ~MM_U() = default; + +private: + void Initialize(Kernel::HLERequestContext& ctx); + void SetAndWait(Kernel::HLERequestContext& ctx); + void Get(Kernel::HLERequestContext& ctx); + + u32 min{0}; + u32 max{0}; + u32 current{0}; +}; + +/// Registers all MM services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::MM diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp new file mode 100644 index 000000000..2a9f84037 --- /dev/null +++ b/src/core/hle/service/nfp/nfp.cpp @@ -0,0 +1,162 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/nfp/nfp.h" +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service::NFP { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +class IUser final : public ServiceFramework<IUser> { +public: + IUser() : ServiceFramework("IUser") { + static const FunctionInfo functions[] = { + {0, &IUser::Initialize, "Initialize"}, + {1, nullptr, "Finalize"}, + {2, &IUser::ListDevices, "ListDevices"}, + {3, nullptr, "StartDetection"}, + {4, nullptr, "StopDetection"}, + {5, nullptr, "Mount"}, + {6, nullptr, "Unmount"}, + {7, nullptr, "OpenApplicationArea"}, + {8, nullptr, "GetApplicationArea"}, + {9, nullptr, "SetApplicationArea"}, + {10, nullptr, "Flush"}, + {11, nullptr, "Restore"}, + {12, nullptr, "CreateApplicationArea"}, + {13, nullptr, "GetTagInfo"}, + {14, nullptr, "GetRegisterInfo"}, + {15, nullptr, "GetCommonInfo"}, + {16, nullptr, "GetModelInfo"}, + {17, &IUser::AttachActivateEvent, "AttachActivateEvent"}, + {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, + {19, &IUser::GetState, "GetState"}, + {20, &IUser::GetDeviceState, "GetDeviceState"}, + {21, &IUser::GetNpadId, "GetNpadId"}, + {22, nullptr, "GetApplicationArea2"}, + {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, + {24, nullptr, "RecreateApplicationArea"}, + }; + RegisterHandlers(functions); + + activate_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:ActivateEvent"); + deactivate_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:DeactivateEvent"); + availability_change_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent"); + } + +private: + enum class State : u32 { + NonInitialized = 0, + Initialized = 1, + }; + + enum class DeviceState : u32 { + Initialized = 0, + }; + + void Initialize(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NFP, "(STUBBED) called"); + + state = State::Initialized; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ListDevices(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 array_size = rp.Pop<u32>(); + + ctx.WriteBuffer(&device_handle, sizeof(device_handle)); + + NGLOG_WARNING(Service_NFP, "(STUBBED) called, array_size={}", array_size); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + } + + void AttachActivateEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 dev_handle = rp.Pop<u64>(); + NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(activate_event); + } + + void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 dev_handle = rp.Pop<u64>(); + NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(deactivate_event); + } + + void GetState(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NFP, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(state)); + } + + void GetDeviceState(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NFP, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(device_state)); + } + + void GetNpadId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 dev_handle = rp.Pop<u64>(); + NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(npad_id); + } + + void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 dev_handle = rp.Pop<u64>(); + NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(availability_change_event); + } + + const u64 device_handle{0xDEAD}; + const HID::ControllerID npad_id{HID::Controller_Player1}; + State state{State::NonInitialized}; + DeviceState device_state{DeviceState::Initialized}; + Kernel::SharedPtr<Kernel::Event> activate_event; + Kernel::SharedPtr<Kernel::Event> deactivate_event; + Kernel::SharedPtr<Kernel::Event> availability_change_event; +}; + +void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_NFP, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IUser>(); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<NFP_User>(module)->InstallAsService(service_manager); +} + +} // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h new file mode 100644 index 000000000..262a666cb --- /dev/null +++ b/src/core/hle/service/nfp/nfp.h @@ -0,0 +1,26 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::NFP { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void CreateUserInterface(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp new file mode 100644 index 000000000..b608fe693 --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service::NFP { + +NFP_User::NFP_User(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "nfp:user") { + static const FunctionInfo functions[] = { + {0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h new file mode 100644 index 000000000..700043114 --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nfp/nfp.h" + +namespace Service::NFP { + +class NFP_User final : public Module::Interface { +public: + explicit NFP_User(std::shared_ptr<Module> module); +}; + +} // namespace Service::NFP diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index e6f05eae5..62489c7fe 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -9,8 +9,7 @@ #include "core/hle/service/nifm/nifm_s.h" #include "core/hle/service/nifm/nifm_u.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { class IScanRequest final : public ServiceFramework<IScanRequest> { public: @@ -32,14 +31,14 @@ public: {0, &IRequest::GetRequestState, "GetRequestState"}, {1, &IRequest::GetResult, "GetResult"}, {2, &IRequest::GetSystemEventReadableHandles, "GetSystemEventReadableHandles"}, - {3, nullptr, "Cancel"}, + {3, &IRequest::Cancel, "Cancel"}, {4, nullptr, "Submit"}, {5, nullptr, "SetRequirement"}, {6, nullptr, "SetRequirementPreset"}, {8, nullptr, "SetPriority"}, {9, nullptr, "SetNetworkProfileId"}, {10, nullptr, "SetRejectable"}, - {11, nullptr, "SetConnectionConfirmationOption"}, + {11, &IRequest::SetConnectionConfirmationOption, "SetConnectionConfirmationOption"}, {12, nullptr, "SetPersistent"}, {13, nullptr, "SetInstant"}, {14, nullptr, "SetSustainable"}, @@ -63,24 +62,37 @@ public: private: void GetRequestState(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); } + void GetResult(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); } + void GetSystemEventReadableHandles(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 2}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(event1, event2); } + void Cancel(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetConnectionConfirmationOption(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + Kernel::SharedPtr<Kernel::Event> event1, event2; }; @@ -96,13 +108,56 @@ public: } }; +class IGeneralService final : public ServiceFramework<IGeneralService> { +public: + IGeneralService(); + +private: + void GetClientId(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); + } + void CreateScanRequest(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IScanRequest>(); + + NGLOG_DEBUG(Service_NIFM, "called"); + } + void CreateRequest(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IRequest>(); + + NGLOG_DEBUG(Service_NIFM, "called"); + } + void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<INetworkProfile>(); + + NGLOG_DEBUG(Service_NIFM, "called"); + } +}; + IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"}, {4, &IGeneralService::CreateRequest, "CreateRequest"}, - {6, nullptr, "GetCurrentNetworkProfile"}, - {7, nullptr, "EnumerateNetworkInterfaces"}, + {5, nullptr, "GetCurrentNetworkProfile"}, + {6, nullptr, "EnumerateNetworkInterfaces"}, + {7, nullptr, "EnumerateNetworkProfiles"}, {8, nullptr, "GetNetworkProfile"}, {9, nullptr, "SetNetworkProfile"}, {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"}, @@ -137,51 +192,28 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { RegisterHandlers(functions); } -void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0); -} - -void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) { +void Module::Interface::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IScanRequest>(); - - LOG_DEBUG(Service_NIFM, "called"); + rb.PushIpcInterface<IGeneralService>(); + NGLOG_DEBUG(Service_NIFM, "called"); } -void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) { +void Module::Interface::CreateGeneralService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IRequest>(); - - LOG_DEBUG(Service_NIFM, "called"); -} - -void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IGeneralService>(); + NGLOG_DEBUG(Service_NIFM, "called"); } -void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<INetworkProfile>(); - - LOG_DEBUG(Service_NIFM, "called"); -} +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<NIFM_A>()->InstallAsService(service_manager); - std::make_shared<NIFM_S>()->InstallAsService(service_manager); - std::make_shared<NIFM_U>()->InstallAsService(service_manager); + auto module = std::make_shared<Module>(); + std::make_shared<NIFM_A>(module)->InstallAsService(service_manager); + std::make_shared<NIFM_S>(module)->InstallAsService(service_manager); + std::make_shared<NIFM_U>(module)->InstallAsService(service_manager); } -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 6edbfe4a4..4ad3f3bcf 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -6,22 +6,22 @@ #include "core/hle/service/service.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -class IGeneralService final : public ServiceFramework<IGeneralService> { +class Module final { public: - IGeneralService(); - -private: - void GetClientId(Kernel::HLERequestContext& ctx); - void CreateScanRequest(Kernel::HLERequestContext& ctx); - void CreateRequest(Kernel::HLERequestContext& ctx); - void RemoveNetworkProfile(Kernel::HLERequestContext& ctx); - void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx); + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx); + void CreateGeneralService(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; }; void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_a.cpp b/src/core/hle/service/nifm/nifm_a.cpp index ee61d8ff4..b7f296a20 100644 --- a/src/core/hle/service/nifm/nifm_a.cpp +++ b/src/core/hle/service/nifm/nifm_a.cpp @@ -2,29 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nifm/nifm_a.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -void NIFM_A::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -void NIFM_A::CreateGeneralService(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -NIFM_A::NIFM_A() : ServiceFramework("nifm:a") { +NIFM_A::NIFM_A(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:a") { static const FunctionInfo functions[] = { {4, &NIFM_A::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NIFM_A::CreateGeneralService, "CreateGeneralService"}, @@ -32,5 +14,4 @@ NIFM_A::NIFM_A() : ServiceFramework("nifm:a") { RegisterHandlers(functions); } -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_a.h b/src/core/hle/service/nifm/nifm_a.h index 06a92a93c..c3ba33110 100644 --- a/src/core/hle/service/nifm/nifm_a.h +++ b/src/core/hle/service/nifm/nifm_a.h @@ -4,21 +4,13 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/service.h" +#include "core/hle/service/nifm/nifm.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -class NIFM_A final : public ServiceFramework<NIFM_A> { +class NIFM_A final : public Module::Interface { public: - NIFM_A(); - ~NIFM_A() = default; - -private: - void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx); - void CreateGeneralService(Kernel::HLERequestContext& ctx); + explicit NIFM_A(std::shared_ptr<Module> module); }; -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_s.cpp b/src/core/hle/service/nifm/nifm_s.cpp index c38b2a4c7..96e3c0cee 100644 --- a/src/core/hle/service/nifm/nifm_s.cpp +++ b/src/core/hle/service/nifm/nifm_s.cpp @@ -2,29 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nifm/nifm_s.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -void NIFM_S::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -void NIFM_S::CreateGeneralService(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -NIFM_S::NIFM_S() : ServiceFramework("nifm:s") { +NIFM_S::NIFM_S(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:s") { static const FunctionInfo functions[] = { {4, &NIFM_S::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NIFM_S::CreateGeneralService, "CreateGeneralService"}, @@ -32,5 +14,4 @@ NIFM_S::NIFM_S() : ServiceFramework("nifm:s") { RegisterHandlers(functions); } -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_s.h b/src/core/hle/service/nifm/nifm_s.h index d11a1ec29..8d1635a5d 100644 --- a/src/core/hle/service/nifm/nifm_s.h +++ b/src/core/hle/service/nifm/nifm_s.h @@ -4,21 +4,13 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/service.h" +#include "core/hle/service/nifm/nifm.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -class NIFM_S final : public ServiceFramework<NIFM_S> { +class NIFM_S final : public Module::Interface { public: - NIFM_S(); - ~NIFM_S() = default; - -private: - void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx); - void CreateGeneralService(Kernel::HLERequestContext& ctx); + explicit NIFM_S(std::shared_ptr<Module> module); }; -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_u.cpp b/src/core/hle/service/nifm/nifm_u.cpp index a5895c13c..8cb75b903 100644 --- a/src/core/hle/service/nifm/nifm_u.cpp +++ b/src/core/hle/service/nifm/nifm_u.cpp @@ -2,29 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nifm/nifm_u.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -void NIFM_U::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -void NIFM_U::CreateGeneralService(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); -} - -NIFM_U::NIFM_U() : ServiceFramework("nifm:u") { +NIFM_U::NIFM_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:u") { static const FunctionInfo functions[] = { {4, &NIFM_U::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NIFM_U::CreateGeneralService, "CreateGeneralService"}, @@ -32,5 +14,4 @@ NIFM_U::NIFM_U() : ServiceFramework("nifm:u") { RegisterHandlers(functions); } -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm_u.h b/src/core/hle/service/nifm/nifm_u.h index da40b604f..def9726b1 100644 --- a/src/core/hle/service/nifm/nifm_u.h +++ b/src/core/hle/service/nifm/nifm_u.h @@ -4,21 +4,13 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/service.h" +#include "core/hle/service/nifm/nifm.h" -namespace Service { -namespace NIFM { +namespace Service::NIFM { -class NIFM_U final : public ServiceFramework<NIFM_U> { +class NIFM_U final : public Module::Interface { public: - NIFM_U(); - ~NIFM_U() = default; - -private: - void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx); - void CreateGeneralService(Kernel::HLERequestContext& ctx); + explicit NIFM_U(std::shared_ptr<Module> module); }; -} // namespace NIFM -} // namespace Service +} // namespace Service::NIFM diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 45681c50f..89c703310 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -5,12 +5,10 @@ #include "core/hle/service/ns/ns.h" #include "core/hle/service/ns/pl_u.h" -namespace Service { -namespace NS { +namespace Service::NS { void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<PL_U>()->InstallAsService(service_manager); } -} // namespace NS -} // namespace Service +} // namespace Service::NS diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index a4b7e3ded..b81ca8f1e 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -6,11 +6,9 @@ #include "core/hle/service/service.h" -namespace Service { -namespace NS { +namespace Service::NS { /// Registers all NS services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace NS -} // namespace Service +} // namespace Service::NS diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index cc9d03a7c..636af9a1e 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -4,11 +4,11 @@ #include "common/common_paths.h" #include "common/file_util.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/ns/pl_u.h" -namespace Service { -namespace NS { +namespace Service::NS { struct FontRegion { u32 offset; @@ -32,10 +32,13 @@ enum class LoadState : u32 { PL_U::PL_U() : ServiceFramework("pl:u") { static const FunctionInfo functions[] = { + {0, &PL_U::RequestLoad, "RequestLoad"}, {1, &PL_U::GetLoadState, "GetLoadState"}, {2, &PL_U::GetSize, "GetSize"}, {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, - {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}}; + {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, + {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, + }; RegisterHandlers(functions); // Attempt to load shared font data from disk @@ -43,21 +46,30 @@ PL_U::PL_U() : ServiceFramework("pl:u") { FileUtil::CreateFullPath(filepath); // Create path if not already created FileUtil::IOFile file(filepath, "rb"); + shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); if (file.IsOpen()) { // Read shared font data ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE); - shared_font = std::make_shared<std::vector<u8>>(static_cast<size_t>(file.GetSize())); file.ReadBytes(shared_font->data(), shared_font->size()); } else { - LOG_WARNING(Service_NS, "Unable to load shared font: %s", filepath.c_str()); + NGLOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath); } } +void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 shared_font_type{rp.Pop<u32>()}; + + NGLOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(LoadState::Done)); @@ -67,7 +79,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size); @@ -77,35 +89,56 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset); } void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { - if (shared_font != nullptr) { - // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared - // font data. This (likely) relies on exact address, size, and offsets from the original - // dump. In the future, we need to replace this with a more robust solution. - - // Map backing memory for the font data - Kernel::g_current_process->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, shared_font, 0, - SHARED_FONT_MEM_SIZE, - Kernel::MemoryState::Shared); - - // Create shared font memory object - shared_font_mem = Kernel::SharedMemory::Create( - Kernel::g_current_process, SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, - "PL_U:shared_font_mem"); - } + // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared + // font data. This (likely) relies on exact address, size, and offsets from the original + // dump. In the future, we need to replace this with a more robust solution. + + // Map backing memory for the font data + Core::CurrentProcess()->vm_manager.MapMemoryBlock( + SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared); + + // Create shared font memory object + shared_font_mem = Kernel::SharedMemory::Create( + Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, + "PL_U:shared_font_mem"); - LOG_DEBUG(Service_NS, "called"); + NGLOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(shared_font_mem); } -} // namespace NS -} // namespace Service +void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for + NGLOG_DEBUG(Service_NS, "called, language_code=%lx", language_code); + IPC::ResponseBuilder rb{ctx, 4}; + std::vector<u32> font_codes; + std::vector<u32> font_offsets; + std::vector<u32> font_sizes; + + // TODO(ogniK): Have actual priority order + for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) { + font_codes.push_back(static_cast<u32>(i)); + font_offsets.push_back(SHARED_FONT_REGIONS[i].offset); + font_sizes.push_back(SHARED_FONT_REGIONS[i].size); + } + + ctx.WriteBuffer(font_codes.data(), font_codes.size(), 0); + ctx.WriteBuffer(font_offsets.data(), font_offsets.size(), 1); + ctx.WriteBuffer(font_sizes.data(), font_sizes.size(), 2); + + rb.Push(RESULT_SUCCESS); + rb.Push<u8>(static_cast<u8>(LoadState::Done)); // Fonts Loaded + rb.Push<u32>(static_cast<u32>(font_codes.size())); +} + +} // namespace Service::NS diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index 7a4766338..fcc2acab7 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -8,8 +8,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/service.h" -namespace Service { -namespace NS { +namespace Service::NS { class PL_U final : public ServiceFramework<PL_U> { public: @@ -17,10 +16,12 @@ public: ~PL_U() = default; private: + void RequestLoad(Kernel::HLERequestContext& ctx); void GetLoadState(Kernel::HLERequestContext& ctx); void GetSize(Kernel::HLERequestContext& ctx); void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx); void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); + void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); /// Handle to shared memory region designated for a shared font Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; @@ -29,5 +30,4 @@ private: std::shared_ptr<std::vector<u8>> shared_font; }; -} // namespace NS -} // namespace Service +} // namespace Service::NS diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index cdc25b059..0f02a1a18 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -9,9 +9,7 @@ #include "common/common_types.h" #include "common/swap.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { /// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to /// implement the ioctl interface. @@ -38,6 +36,4 @@ public: virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0; }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 7674d332d..103e66d0c 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -10,32 +10,27 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform) { VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle); - LOG_WARNING(Service, - "Drawing from address %llx offset %08X Width %u Height %u Stride %u Format %u", - addr, offset, width, height, stride, format); + NGLOG_WARNING(Service, + "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}", + addr, offset, width, height, stride, format); - using PixelFormat = RendererBase::FramebufferInfo::PixelFormat; - using Flags = NVFlinger::BufferQueue::BufferTransformFlags; - const bool flip_vertical = static_cast<u32>(transform) & static_cast<u32>(Flags::FlipV); - const RendererBase::FramebufferInfo framebuffer_info{ - addr, offset, width, height, stride, static_cast<PixelFormat>(format), flip_vertical}; + using PixelFormat = Tegra::FramebufferConfig::PixelFormat; + const Tegra::FramebufferConfig framebuffer{ + addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform}; Core::System::GetInstance().perf_stats.EndGameFrame(); - VideoCore::g_renderer->SwapBuffers(framebuffer_info); + + VideoCore::g_renderer->SwapBuffers(framebuffer); } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 66f56f23d..3d3979723 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -10,9 +10,7 @@ #include "core/hle/service/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvflinger/buffer_queue.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvmap; @@ -31,6 +29,4 @@ private: std::shared_ptr<nvmap> nvmap_dev; }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices 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 9892402fa..c1eea861d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -9,13 +9,11 @@ #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" #include "core/hle/service/nvdrv/devices/nvmap.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", - command.raw, input.size(), output.size()); + NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", + command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocInitalizeExCommand: @@ -28,23 +26,29 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vecto return BindChannel(input, output); case IoctlCommand::IocGetVaRegionsCommand: return GetVARegions(input, output); + case IoctlCommand::IocUnmapBufferCommand: + return UnmapBuffer(input, output); } + + if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) + return Remap(input, output); + + UNIMPLEMENTED_MSG("Unimplemented ioctl command"); return 0; } u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) { IoctlInitalizeEx params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x%x", params.big_page_size); - std::memcpy(output.data(), ¶ms, output.size()); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size); return 0; } u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocSpace params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, pages=%x, page_size=%x, flags=%x", params.pages, - params.page_size, params.flags); + NGLOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages, + params.page_size, params.flags); auto& gpu = Core::System::GetInstance().GPU(); const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)}; @@ -58,15 +62,45 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& return 0; } +u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { + size_t num_entries = input.size() / sizeof(IoctlRemapEntry); + + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries); + + std::vector<IoctlRemapEntry> entries(num_entries); + std::memcpy(entries.data(), input.data(), input.size()); + + auto& gpu = Core::System::GetInstance().GPU(); + + for (const auto& entry : entries) { + NGLOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", + entry.offset, entry.nvmap_handle, entry.pages); + Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10; + + auto object = nvmap_dev->GetObject(entry.nvmap_handle); + ASSERT(object); + + ASSERT(object->status == nvmap::Object::Status::Allocated); + + u64 size = static_cast<u64>(entry.pages) << 0x10; + ASSERT(size <= object->size); + + Tegra::GPUVAddr returned = gpu.memory_manager->MapBufferEx(object->addr, offset, size); + ASSERT(returned == offset); + } + std::memcpy(output.data(), entries.data(), output.size()); + return 0; +} + u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { IoctlMapBufferEx params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, - "called, flags=%x, nvmap_handle=%x, buffer_offset=%" PRIu64 ", mapping_size=%" PRIu64 - ", offset=%" PRIu64, - params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size, - params.offset); + NGLOG_DEBUG(Service_NVDRV, + "called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}" + ", offset={}", + params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size, + params.offset); if (!params.nvmap_handle) { return 0; @@ -75,6 +109,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou auto object = nvmap_dev->GetObject(params.nvmap_handle); ASSERT(object); + // We can only map objects that have already been assigned a CPU address. + ASSERT(object->status == nvmap::Object::Status::Allocated); + + ASSERT(params.buffer_offset == 0); + + // The real nvservices doesn't make a distinction between handles and ids, and + // object can only have one handle and it will be the same as its id. Assert that this is the + // case to prevent unexpected behavior. + ASSERT(object->id == params.nvmap_handle); + auto& gpu = Core::System::GetInstance().GPU(); if (params.flags & 1) { @@ -83,6 +127,37 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou params.offset = gpu.memory_manager->MapBufferEx(object->addr, object->size); } + // Create a new mapping entry for this operation. + ASSERT_MSG(buffer_mappings.find(params.offset) == buffer_mappings.end(), + "Offset is already mapped"); + + BufferMapping mapping{}; + mapping.nvmap_handle = params.nvmap_handle; + mapping.offset = params.offset; + mapping.size = object->size; + + buffer_mappings[params.offset] = mapping; + + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlUnmapBuffer params{}; + std::memcpy(¶ms, input.data(), input.size()); + + NGLOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset); + + auto& gpu = Core::System::GetInstance().GPU(); + + auto itr = buffer_mappings.find(params.offset); + + ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping"); + + params.offset = gpu.memory_manager->UnmapBuffer(params.offset, itr->second.size); + + buffer_mappings.erase(itr->second.offset); + std::memcpy(output.data(), ¶ms, output.size()); return 0; } @@ -90,17 +165,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) { IoctlBindChannel params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.fd); + NGLOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); channel = params.fd; - std::memcpy(output.data(), ¶ms, output.size()); return 0; } u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) { IoctlGetVaRegions params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr=%" PRIu64 ", buf_size=%x", - params.buf_addr, params.buf_size); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr, + params.buf_size); params.buf_size = 0x30; params.regions[0].offset = 0x04000000; @@ -115,6 +189,4 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o return 0; } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index f8a60cce7..d4c4b4db3 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -5,15 +5,14 @@ #pragma once #include <memory> +#include <unordered_map> #include <utility> #include <vector> #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvmap; @@ -28,9 +27,11 @@ private: enum class IoctlCommand : u32_le { IocInitalizeExCommand = 0x40284109, IocAllocateSpaceCommand = 0xC0184102, + IocRemapCommand = 0x00000014, IocMapBufferExCommand = 0xC0284106, IocBindChannelCommand = 0x40044101, IocGetVaRegionsCommand = 0xC0404108, + IocUnmapBufferCommand = 0xC0084105, }; struct IoctlInitalizeEx { @@ -56,6 +57,16 @@ private: }; static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size"); + struct IoctlRemapEntry { + u16_le flags; + u16_le kind; + u32_le nvmap_handle; + INSERT_PADDING_WORDS(1); + u32_le offset; + u32_le pages; + }; + static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); + struct IoctlMapBufferEx { u32_le flags; // bit0: fixed_offset, bit2: cacheable u32_le kind; // -1 is default @@ -67,6 +78,11 @@ private: }; static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size"); + struct IoctlUnmapBuffer { + u64_le offset; + }; + static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size"); + struct IoctlBindChannel { u32_le fd; }; @@ -89,17 +105,26 @@ private: static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2, "IoctlGetVaRegions is incorrect size"); + struct BufferMapping { + u64 offset; + u64 size; + u32 nvmap_handle; + }; + + /// Map containing the nvmap object mappings in GPU memory. + std::unordered_map<u64, BufferMapping> buffer_mappings; + u32 channel{}; u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output); u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output); + u32 Remap(const std::vector<u8>& input, std::vector<u8>& output); u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); + u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output); u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output); std::shared_ptr<nvmap> nvmap_dev; }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index 45711d686..7872d1e09 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -6,29 +6,31 @@ #include "common/logging/log.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", - command.raw, input.size(), output.size()); + NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", + command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocGetConfigCommand: return NvOsGetConfigU32(input, output); case IoctlCommand::IocCtrlEventWaitCommand: - return IocCtrlEventWait(input, output); + return IocCtrlEventWait(input, output, false); + case IoctlCommand::IocCtrlEventWaitAsyncCommand: + return IocCtrlEventWait(input, output, true); + case IoctlCommand::IocCtrlEventRegisterCommand: + return IocCtrlEventRegister(input, output); } - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { IocGetConfigParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_DEBUG(Service_NVDRV, "called, setting=%s!%s", params.domain_str.data(), - params.param_str.data()); + NGLOG_DEBUG(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(), + params.param_str.data()); if (!strcmp(params.domain_str.data(), "nv")) { if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) { @@ -38,7 +40,7 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& } else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) { params.config_str[0] = '0'; } else { - params.config_str[0] = '0'; + params.config_str[0] = '\0'; } } else { UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware @@ -47,11 +49,13 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& return 0; } -u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, + bool is_async) { IocCtrlEventWaitParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, syncpt_id=%u threshold=%u timeout=%d", - params.syncpt_id, params.threshold, params.timeout); + NGLOG_WARNING(Service_NVDRV, + "(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}", + params.syncpt_id, params.threshold, params.timeout, is_async); // TODO(Subv): Implement actual syncpt waiting. params.value = 0; @@ -59,6 +63,10 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& return 0; } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) { + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); + // TODO(bunnei): Implement this. + return 0; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 0ca01aa6d..090261a60 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -11,9 +11,7 @@ #include "common/common_types.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvhost_ctrl final : public nvdevice { public: @@ -28,12 +26,64 @@ private: IocSyncptIncrCommand = 0x40040015, IocSyncptWaitCommand = 0xC00C0016, IocModuleMutexCommand = 0x40080017, - IocModuleRegRDWRCommand = 0xC008010E, + IocModuleRegRDWRCommand = 0xC0180018, IocSyncptWaitexCommand = 0xC0100019, IocSyncptReadMaxCommand = 0xC008001A, - IocCtrlEventWaitCommand = 0xC010001D, IocGetConfigCommand = 0xC183001B, + IocCtrlEventSignalCommand = 0xC004001C, + IocCtrlEventWaitCommand = 0xC010001D, + IocCtrlEventWaitAsyncCommand = 0xC010001E, + IocCtrlEventRegisterCommand = 0xC004001F, + IocCtrlEventUnregisterCommand = 0xC0040020, + IocCtrlEventKillCommand = 0x40080021, + }; + struct IocSyncptReadParams { + u32_le id; + u32_le value; + }; + static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size"); + + struct IocSyncptIncrParams { + u32_le id; + }; + static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size"); + + struct IocSyncptWaitParams { + u32_le id; + u32_le thresh; + s32_le timeout; + }; + static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size"); + + struct IocModuleMutexParams { + u32_le id; + u32_le lock; // (0 = unlock and 1 = lock) + }; + static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size"); + + struct IocModuleRegRDWRParams { + u32_le id; + u32_le num_offsets; + u32_le block_size; + u32_le offsets; + u32_le values; + u32_le write; + }; + static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size"); + + struct IocSyncptWaitexParams { + u32_le id; + u32_le thresh; + s32_le timeout; + u32_le value; }; + static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size"); + + struct IocSyncptReadMaxParams { + u32_le id; + u32_le value; + }; + static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size"); struct IocGetConfigParams { std::array<char, 0x41> domain_str; @@ -42,6 +92,12 @@ private: }; static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size"); + struct IocCtrlEventSignalParams { + u32_le user_event_id; + }; + static_assert(sizeof(IocCtrlEventSignalParams) == 4, + "IocCtrlEventSignalParams is incorrect size"); + struct IocCtrlEventWaitParams { u32_le syncpt_id; u32_le threshold; @@ -50,11 +106,37 @@ private: }; static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size"); + struct IocCtrlEventWaitAsyncParams { + u32_le syncpt_id; + u32_le threshold; + u32_le timeout; + u32_le value; + }; + static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16, + "IocCtrlEventWaitAsyncParams is incorrect size"); + + struct IocCtrlEventRegisterParams { + u32_le user_event_id; + }; + static_assert(sizeof(IocCtrlEventRegisterParams) == 4, + "IocCtrlEventRegisterParams is incorrect size"); + + struct IocCtrlEventUnregisterParams { + u32_le user_event_id; + }; + static_assert(sizeof(IocCtrlEventUnregisterParams) == 4, + "IocCtrlEventUnregisterParams is incorrect size"); + + struct IocCtrlEventKill { + u64_le user_events; + }; + static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size"); + u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output); + u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async); + + u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 3b353d742..0abc0de83 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -7,13 +7,11 @@ #include "common/logging/log.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", - command.raw, input.size(), output.size()); + NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", + command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocGetCharacteristicsCommand: @@ -26,13 +24,19 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec return ZCullGetCtxSize(input, output); case IoctlCommand::IocZcullGetInfo: return ZCullGetInfo(input, output); + case IoctlCommand::IocZbcSetTable: + return ZBCSetTable(input, output); + case IoctlCommand::IocZbcQueryTable: + return ZBCQueryTable(input, output); + case IoctlCommand::IocFlushL2: + return FlushL2(input, output); } - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); params.gc.arch = 0x120; @@ -79,14 +83,19 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) { IoctlGpuGetTpcMasksArgs params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, mask=0x%x, mask_buf_addr=0x%" PRIx64, - params.mask_buf_size, params.mask_buf_addr); + NGLOG_INFO(Service_NVDRV, "called, mask=0x{:X}, mask_buf_addr=0x{:X}", params.mask_buf_size, + params.mask_buf_addr); + // TODO(ogniK): Confirm value on hardware + if (params.mask_buf_size) + params.tpc_mask_size = 4 * 1; // 4 * num_gpc + else + params.tpc_mask_size = 0; std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; } u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlActiveSlotMask params{}; std::memcpy(¶ms, input.data(), input.size()); params.slot = 0x07; @@ -96,7 +105,7 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector } u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlZcullGetCtxSize params{}; std::memcpy(¶ms, input.data(), input.size()); params.size = 0x1; @@ -105,7 +114,7 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u } u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlNvgpuGpuZcullGetInfoArgs params{}; std::memcpy(¶ms, input.data(), input.size()); params.width_align_pixels = 0x20; @@ -122,6 +131,31 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& return 0; } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) { + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlZbcSetTable params{}; + std::memcpy(¶ms, input.data(), input.size()); + // TODO(ogniK): What does this even actually do? + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) { + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlZbcQueryTable params{}; + std::memcpy(¶ms, input.data(), input.size()); + // TODO : To implement properly + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) { + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlFlushL2 params{}; + std::memcpy(¶ms, input.data(), input.size()); + // TODO : To implement properly + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index dc0476993..f09113e67 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -9,9 +9,7 @@ #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvhost_ctrl_gpu final : public nvdevice { public: @@ -27,6 +25,19 @@ private: IocGetActiveSlotMaskCommand = 0x80084714, IocZcullGetCtxSizeCommand = 0x80044701, IocZcullGetInfo = 0x80284702, + IocZbcSetTable = 0x402C4703, + IocZbcQueryTable = 0xC0344704, + IocFlushL2 = 0x40084707, + IocInvalICache = 0x4008470D, + IocSetMmudebugMode = 0x4008470E, + IocSetSmDebugMode = 0x4010470F, + IocWaitForPause = 0xC0084710, + IocGetTcpExceptionEnStatus = 0x80084711, + IocNumVsms = 0x80084712, + IocVsmsMapping = 0xC0044713, + IocGetErrorChannelUserData = 0xC008471B, + IocGetGpuTime = 0xC010471C, + IocGetCpuTimeCorrelationInfo = 0xC108471D, }; struct IoctlGpuCharacteristics { @@ -88,7 +99,7 @@ private: /// [in] pointer to TPC mask buffer. It will receive one 32-bit TPC mask per GPC or 0 if /// GPC is not enabled or not present. This parameter is ignored if mask_buf_size is 0. u64_le mask_buf_addr; - u64_le unk; // Nintendo add this? + u64_le tpc_mask_size; // Nintendo add this? }; static_assert(sizeof(IoctlGpuGetTpcMasksArgs) == 24, "IoctlGpuGetTpcMasksArgs is incorrect size"); @@ -119,12 +130,40 @@ private: static_assert(sizeof(IoctlNvgpuGpuZcullGetInfoArgs) == 40, "IoctlNvgpuGpuZcullGetInfoArgs is incorrect size"); + struct IoctlZbcSetTable { + u32_le color_ds[4]; + u32_le color_l2[4]; + u32_le depth; + u32_le format; + u32_le type; + }; + static_assert(sizeof(IoctlZbcSetTable) == 44, "IoctlZbcSetTable is incorrect size"); + + struct IoctlZbcQueryTable { + u32_le color_ds[4]; + u32_le color_l2[4]; + u32_le depth; + u32_le ref_cnt; + u32_le format; + u32_le type; + u32_le index_size; + }; + static_assert(sizeof(IoctlZbcQueryTable) == 52, "IoctlZbcQueryTable is incorrect size"); + + struct IoctlFlushL2 { + u32_le flush; // l2_flush | l2_invalidate << 1 | fb_flush << 2 + u32_le reserved; + }; + static_assert(sizeof(IoctlFlushL2) == 8, "IoctlFlushL2 is incorrect size"); + u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output); + u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output); + u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output); + u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output); }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index da44c65f3..79aab87f9 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -9,13 +9,11 @@ #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", - command.raw, input.size(), output.size()); + NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", + command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocSetNVMAPfdCommand: @@ -34,6 +32,10 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u return AllocGPFIFOEx2(input, output); case IoctlCommand::IocAllocObjCtxCommand: return AllocateObjectContext(input, output); + case IoctlCommand::IocChannelGetWaitbaseCommand: + return GetWaitbase(input, output); + case IoctlCommand::IocChannelSetTimeoutCommand: + return ChannelSetTimeout(input, output); } if (command.group == NVGPU_IOCTL_MAGIC) { @@ -42,30 +44,28 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u } } - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; }; u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.nvmap_fd); + NGLOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; - std::memcpy(output.data(), ¶ms, output.size()); return 0; } u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); user_data = params.data; - std::memcpy(output.data(), ¶ms, output.size()); return 0; } u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); params.data = user_data; @@ -75,8 +75,8 @@ u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& out u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) { std::memcpy(&zcull_params, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, gpu_va=%" PRIx64 ", mode=%x", zcull_params.gpu_va, - zcull_params.mode); + NGLOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va, + zcull_params.mode); std::memcpy(output.data(), &zcull_params, output.size()); return 0; } @@ -84,26 +84,26 @@ u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetErrorNotifier params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset=%" PRIx64 ", size=%" PRIx64 ", mem=%x", - params.offset, params.size, params.mem); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", + params.offset, params.size, params.mem); std::memcpy(output.data(), ¶ms, output.size()); return 0; } u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { std::memcpy(&channel_priority, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority=%x", channel_priority); - std::memcpy(output.data(), &channel_priority, output.size()); + NGLOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority); return 0; } u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocGpfifoEx2 params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, - "(STUBBED) called, num_entries=%x, flags=%x, unk0=%x, unk1=%x, unk2=%x, unk3=%x", - params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, - params.unk3); + NGLOG_WARNING(Service_NVDRV, + "(STUBBED) called, num_entries={:X}, flags={:X}, unk0={:X}, " + "unk1={:X}, unk2={:X}, unk3={:X}", + params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, + params.unk3); params.fence_out.id = 0; params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, output.size()); @@ -113,8 +113,8 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocObjCtx params{}; std::memcpy(¶ms, input.data(), input.size()); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num=%x, flags=%x", params.class_num, - params.flags); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num, + params.flags); params.obj_id = 0x0; std::memcpy(output.data(), ¶ms, output.size()); return 0; @@ -125,8 +125,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp UNIMPLEMENTED(); IoctlSubmitGpfifo params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo=%" PRIx64 ", num_entries=%x, flags=%x", - params.gpfifo, params.num_entries, params.flags); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", + params.gpfifo, params.num_entries, params.flags); auto entries = std::vector<IoctlGpfifoEntry>(); entries.resize(params.num_entries); @@ -142,6 +142,20 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp return 0; } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetWaitbase params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); + NGLOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown); + params.value = 0; // Seems to be hard coded at 0 + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlChannelSetTimeout params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlChannelSetTimeout)); + NGLOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout); + return 0; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index e7e9a0088..56b5ed60d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -10,9 +10,7 @@ #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvmap; constexpr u32 NVGPU_IOCTL_MAGIC('H'); @@ -28,13 +26,23 @@ public: private: enum class IoctlCommand : u32_le { IocSetNVMAPfdCommand = 0x40044801, + IocAllocGPFIFOCommand = 0x40084805, IocSetClientDataCommand = 0x40084714, IocGetClientDataCommand = 0x80084715, IocZCullBind = 0xc010480b, IocSetErrorNotifierCommand = 0xC018480C, IocChannelSetPriorityCommand = 0x4004480D, + IocEnableCommand = 0x0000480E, + IocDisableCommand = 0x0000480F, + IocPreemptCommand = 0x00004810, + IocForceResetCommand = 0x00004811, + IocEventIdControlCommand = 0x40084812, + IocGetErrorNotificationCommand = 0xC0104817, + IocAllocGPFIFOExCommand = 0x40204818, IocAllocGPFIFOEx2Command = 0xC020481A, IocAllocObjCtxCommand = 0xC0104809, + IocChannelGetWaitbaseCommand = 0xC0080003, + IocChannelSetTimeoutCommand = 0x40044803, }; enum class CtxObjects : u32_le { @@ -51,6 +59,17 @@ private: }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + struct IoctlChannelSetTimeout { + u32_le timeout; + }; + static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size"); + + struct IoctlAllocGPFIFO { + u32_le num_entries; + u32_le flags; + }; + static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size"); + struct IoctlClientData { u64_le data; }; @@ -71,12 +90,45 @@ private: }; static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size"); + struct IoctlChannelSetPriority { + u32_le priority; + }; + static_assert(sizeof(IoctlChannelSetPriority) == 4, + "IoctlChannelSetPriority is incorrect size"); + + struct IoctlEventIdControl { + u32_le cmd; // 0=disable, 1=enable, 2=clear + u32_le id; + }; + static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size"); + + struct IoctlGetErrorNotification { + u64_le timestamp; + u32_le info32; + u16_le info16; + u16_le status; // always 0xFFFF + }; + static_assert(sizeof(IoctlGetErrorNotification) == 16, + "IoctlGetErrorNotification is incorrect size"); + struct IoctlFence { u32_le id; u32_le value; }; static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size"); + struct IoctlAllocGpfifoEx { + u32_le num_entries; + u32_le flags; + u32_le unk0; + u32_le unk1; + u32_le unk2; + u32_le unk3; + u32_le unk4; + u32_le unk5; + }; + static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size"); + struct IoctlAllocGpfifoEx2 { u32_le num_entries; // in u32_le flags; // in @@ -119,7 +171,13 @@ private: IoctlFence fence_out; // returned new fence object for others to wait on }; static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence), - "submit_gpfifo is incorrect size"); + "IoctlSubmitGpfifo is incorrect size"); + + struct IoctlGetWaitbase { + u32 unknown; // seems to be ignored? Nintendo added this + u32 value; + }; + static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size"); u32_le nvmap_fd{}; u64_le user_data{}; @@ -135,10 +193,10 @@ private: u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); + u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); std::shared_ptr<nvmap> nvmap_dev; }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp new file mode 100644 index 000000000..0b6c22898 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -0,0 +1,32 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h" + +namespace Service::Nvidia::Devices { + +u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", + command.raw, input.size(), output.size()); + + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::IocSetNVMAPfdCommand: + return SetNVMAPfd(input, output); + } + + UNIMPLEMENTED_MSG("Unimplemented ioctl"); + return 0; +} + +u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSetNvmapFD params{}; + std::memcpy(¶ms, input.data(), input.size()); + NGLOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; + return 0; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h new file mode 100644 index 000000000..0192aecdd --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -0,0 +1,38 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstdlib> +#include <cstring> +#include <vector> +#include "common/common_types.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service::Nvidia::Devices { + +class nvhost_nvdec final : public nvdevice { +public: + nvhost_nvdec() = default; + ~nvhost_nvdec() override = default; + + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + +private: + enum class IoctlCommand : u32_le { + IocSetNVMAPfdCommand = 0x40044801, + }; + + struct IoctlSetNvmapFD { + u32_le nvmap_fd; + }; + static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + + u32_le nvmap_fd{}; + + u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); +}; + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index b3842eb4c..23fe98190 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -9,9 +9,7 @@ #include "common/logging/log.h" #include "core/hle/service/nvdrv/devices/nvmap.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { VAddr nvmap::GetObjectAddress(u32 handle) const { auto object = GetObject(handle); @@ -32,9 +30,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o return IocFromId(input, output); case IoctlCommand::Param: return IocParam(input, output); + case IoctlCommand::Free: + return IocFree(input, output); } - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } @@ -47,11 +47,12 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { object->id = next_id++; object->size = params.size; object->status = Object::Status::Created; + object->refcount = 1; u32 handle = next_handle++; handles[handle] = std::move(object); - LOG_DEBUG(Service_NVDRV, "size=0x%08X", params.size); + NGLOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); params.handle = handle; @@ -72,7 +73,7 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { object->addr = params.addr; object->status = Object::Status::Allocated; - LOG_DEBUG(Service_NVDRV, "called, addr=0x%" PRIx64, params.addr); + NGLOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; @@ -82,7 +83,7 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { IocGetIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "called"); + NGLOG_WARNING(Service_NVDRV, "called"); auto object = GetObject(params.handle); ASSERT(object); @@ -97,12 +98,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { IocFromIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); auto itr = std::find_if(handles.begin(), handles.end(), [&](const auto& entry) { return entry.second->id == params.id; }); ASSERT(itr != handles.end()); + itr->second->refcount++; + // Return the existing handle instead of creating a new one. params.handle = itr->first; @@ -116,25 +119,25 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { IocParamParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called type=%u", params.type); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param); auto object = GetObject(params.handle); ASSERT(object); ASSERT(object->status == Object::Status::Allocated); - switch (static_cast<ParamTypes>(params.type)) { + switch (static_cast<ParamTypes>(params.param)) { case ParamTypes::Size: - params.value = object->size; + params.result = object->size; break; case ParamTypes::Alignment: - params.value = object->align; + params.result = object->align; break; case ParamTypes::Heap: // TODO(Subv): Seems to be a hardcoded value? - params.value = 0x40000000; + params.result = 0x40000000; break; case ParamTypes::Kind: - params.value = object->kind; + params.result = object->kind; break; default: UNIMPLEMENTED(); @@ -144,6 +147,34 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { return 0; } -} // namespace Devices -} // namespace Nvidia -} // namespace Service +u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { + enum FreeFlags { + Freed = 0, + NotFreedYet = 1, + }; + + IocFreeParams params; + std::memcpy(¶ms, input.data(), sizeof(params)); + + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + auto itr = handles.find(params.handle); + ASSERT(itr != handles.end()); + + itr->second->refcount--; + + params.refcount = itr->second->refcount; + params.size = itr->second->size; + + if (itr->second->refcount == 0) + params.flags = Freed; + else + params.flags = NotFreedYet; + + handles.erase(params.handle); + + std::memcpy(output.data(), ¶ms, sizeof(params)); + return 0; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 4681e438b..39fafaa7c 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -12,9 +12,7 @@ #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -namespace Service { -namespace Nvidia { -namespace Devices { +namespace Service::Nvidia::Devices { class nvmap final : public nvdevice { public: @@ -36,6 +34,7 @@ public: u8 kind; VAddr addr; Status status; + u32 refcount; }; std::shared_ptr<Object> GetObject(u32 handle) const { @@ -60,16 +59,25 @@ private: Create = 0xC0080101, FromId = 0xC0080103, Alloc = 0xC0200104, + Free = 0xC0180105, Param = 0xC00C0109, - GetId = 0xC008010E + GetId = 0xC008010E, }; - struct IocCreateParams { // Input u32_le size; // Output u32_le handle; }; + static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size"); + + struct IocFromIdParams { + // Input + u32_le id; + // Output + u32_le handle; + }; + static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size"); struct IocAllocParams { // Input @@ -81,36 +89,40 @@ private: INSERT_PADDING_BYTES(7); u64_le addr; }; + static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size"); - struct IocGetIdParams { - // Output - u32_le id; - // Input + struct IocFreeParams { u32_le handle; + INSERT_PADDING_BYTES(4); + u64_le refcount; + u32_le size; + u32_le flags; }; + static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size"); - struct IocFromIdParams { + struct IocParamParams { // Input - u32_le id; - // Output u32_le handle; + u32_le param; + // Output + u32_le result; }; + static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size"); - struct IocParamParams { + struct IocGetIdParams { + // Output + u32_le id; // Input u32_le handle; - u32_le type; - // Output - u32_le value; }; + static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output); u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output); u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output); u32 IocFromId(const std::vector<u8>& input, std::vector<u8>& output); u32 IocParam(const std::vector<u8>& input, std::vector<u8>& output); + u32 IocFree(const std::vector<u8>& input, std::vector<u8>& output); }; -} // namespace Devices -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index c70370f1f..45d2862ef 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -9,11 +9,10 @@ #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { void NVDRV::Open(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); const auto& buffer = ctx.ReadBuffer(); std::string device_name(buffer.begin(), buffer.end()); @@ -26,7 +25,7 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) { } void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); @@ -42,7 +41,7 @@ void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { } void NVDRV::Close(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); + NGLOG_DEBUG(Service_NVDRV, "called"); IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); @@ -54,7 +53,7 @@ void NVDRV::Close(Kernel::HLERequestContext& ctx) { } void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); @@ -64,7 +63,7 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); u32 event_id = rp.Pop<u32>(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd=%x, event_id=%x", fd, event_id); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); IPC::ResponseBuilder rb{ctx, 3, 1}; rb.Push(RESULT_SUCCESS); @@ -76,14 +75,14 @@ void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; pid = rp.Pop<u64>(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x%" PRIx64, pid); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); } void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + NGLOG_WARNING(Service_NVDRV, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -96,7 +95,14 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) {2, &NVDRV::Close, "Close"}, {3, &NVDRV::Initialize, "Initialize"}, {4, &NVDRV::QueryEvent, "QueryEvent"}, + {5, nullptr, "MapSharedMem"}, + {6, nullptr, "GetStatus"}, + {7, nullptr, "ForceSetClientPID"}, {8, &NVDRV::SetClientPID, "SetClientPID"}, + {9, nullptr, "DumpGraphicsMemoryInfo"}, + {10, nullptr, "InitializeDevtools"}, + {11, nullptr, "Ioctl2"}, + {12, nullptr, "Ioctl3"}, {13, &NVDRV::FinishInitialize, "FinishInitialize"}, }; RegisterHandlers(functions); @@ -104,5 +110,4 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) query_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NVDRV::query_event"); } -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index daf2302af..959b5ba29 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -10,8 +10,7 @@ #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/service.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { class NVDRV final : public ServiceFramework<NVDRV> { public: @@ -34,5 +33,4 @@ private: Kernel::SharedPtr<Kernel::Event> query_event; }; -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index ea00240e6..cc5cfe34e 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -9,13 +9,13 @@ #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h" #include "core/hle/service/nvdrv/devices/nvmap.h" #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvmemp.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { std::weak_ptr<Module> nvdrv; @@ -37,11 +37,12 @@ Module::Module() { devices["/dev/nvmap"] = nvmap_dev; devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev); devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(); + devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(); } u32 Module::Open(std::string device_name) { - ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device %s", - device_name.c_str()); + ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}", + device_name); auto device = devices[device_name]; u32 fd = next_fd++; @@ -69,5 +70,4 @@ ResultCode Module::Close(u32 fd) { return RESULT_SUCCESS; } -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 6a55ff96d..579940817 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -10,8 +10,7 @@ #include "common/common_types.h" #include "core/hle/service/service.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { namespace Devices { class nvdevice; @@ -61,5 +60,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager); extern std::weak_ptr<Module> nvdrv; -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp index 5a13732c7..9ca6e5512 100644 --- a/src/core/hle/service/nvdrv/nvmemp.cpp +++ b/src/core/hle/service/nvdrv/nvmemp.cpp @@ -8,24 +8,22 @@ #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvmemp.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { NVMEMP::NVMEMP() : ServiceFramework("nvmemp") { static const FunctionInfo functions[] = { - {0, &NVMEMP::Unknown0, "Unknown0"}, - {1, &NVMEMP::Unknown1, "Unknown1"}, + {0, &NVMEMP::Cmd0, "Cmd0"}, + {1, &NVMEMP::Cmd1, "Cmd1"}, }; RegisterHandlers(functions); } -void NVMEMP::Unknown0(Kernel::HLERequestContext& ctx) { +void NVMEMP::Cmd0(Kernel::HLERequestContext& ctx) { UNIMPLEMENTED(); } -void NVMEMP::Unknown1(Kernel::HLERequestContext& ctx) { +void NVMEMP::Cmd1(Kernel::HLERequestContext& ctx) { UNIMPLEMENTED(); } -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h index a6b5fbb82..dfdcabf4a 100644 --- a/src/core/hle/service/nvdrv/nvmemp.h +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Nvidia { +namespace Service::Nvidia { class NVMEMP final : public ServiceFramework<NVMEMP> { public: @@ -15,9 +14,8 @@ public: ~NVMEMP() = default; private: - void Unknown0(Kernel::HLERequestContext& ctx); - void Unknown1(Kernel::HLERequestContext& ctx); + void Cmd0(Kernel::HLERequestContext& ctx); + void Cmd1(Kernel::HLERequestContext& ctx); }; -} // namespace Nvidia -} // namespace Service +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index ff7b6b039..49e88b394 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -23,29 +23,33 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) { buffer.igbp_buffer = igbp_buffer; buffer.status = Buffer::Status::Free; - LOG_WARNING(Service, "Adding graphics buffer %u", slot); + NGLOG_WARNING(Service, "Adding graphics buffer {}", slot); queue.emplace_back(buffer); + + if (buffer_wait_event) { + buffer_wait_event->Signal(); + } } -u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) { +boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) { auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { // Only consider free buffers. Buffers become free once again after they've been Acquired // and Released by the compositor, see the NVFlinger::Compose method. - if (buffer.status != Buffer::Status::Free) + if (buffer.status != Buffer::Status::Free) { return false; + } // Make sure that the parameters match. - auto& igbp_buffer = buffer.igbp_buffer; - return igbp_buffer.format == pixel_format && igbp_buffer.width == width && - igbp_buffer.height == height; + return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height; }); + if (itr == queue.end()) { - LOG_CRITICAL(Service_NVDRV, "no free buffers for pixel_format=%d, width=%d, height=%d", - pixel_format, width, height); - itr = queue.begin(); + return boost::none; } + buffer_wait_event = nullptr; + itr->status = Buffer::Status::Dequeued; return itr->slot; } @@ -83,10 +87,14 @@ void BufferQueue::ReleaseBuffer(u32 slot) { ASSERT(itr != queue.end()); ASSERT(itr->status == Buffer::Status::Acquired); itr->status = Buffer::Status::Free; + + if (buffer_wait_event) { + buffer_wait_event->Signal(); + } } u32 BufferQueue::Query(QueryType type) { - LOG_WARNING(Service, "(STUBBED) called type=%u", static_cast<u32>(type)); + NGLOG_WARNING(Service, "(STUBBED) called type={}", static_cast<u32>(type)); switch (type) { case QueryType::NativeWindowFormat: // TODO(Subv): Use an enum for this @@ -98,5 +106,10 @@ u32 BufferQueue::Query(QueryType type) { return 0; } +void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event) { + ASSERT_MSG(!buffer_wait_event, "buffer_wait_event only supports a single waiting thread!"); + buffer_wait_event = std::move(wait_event); +} + } // namespace NVFlinger } // namespace Service diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index ef9732769..1de5767cb 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -47,6 +47,8 @@ public: ~BufferQueue() = default; enum class BufferTransformFlags : u32 { + /// No transform flags are set + Unset = 0x00, /// Flip source image horizontally (around the vertical axis) FlipH = 0x01, /// Flip source image vertically (around the horizontal axis) @@ -69,12 +71,13 @@ public: }; void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer); - u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height); + boost::optional<u32> DequeueBuffer(u32 width, u32 height); const IGBPBuffer& RequestBuffer(u32 slot) const; void QueueBuffer(u32 slot, BufferTransformFlags transform); boost::optional<const Buffer&> AcquireBuffer(); void ReleaseBuffer(u32 slot); u32 Query(QueryType type); + void SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event); u32 GetId() const { return id; @@ -90,6 +93,9 @@ private: std::vector<Buffer> queue; Kernel::SharedPtr<Kernel::Event> native_handle; + + /// Used to signal waiting thread when no buffers are available + Kernel::SharedPtr<Kernel::Event> buffer_wait_event; }; } // namespace NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index a54239b0f..5c50ed601 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include "common/alignment.h" +#include "common/microprofile.h" #include "common/scope_exit.h" #include "core/core.h" #include "core/core_timing.h" @@ -15,11 +16,10 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" -namespace Service { -namespace NVFlinger { +namespace Service::NVFlinger { constexpr size_t SCREEN_REFRESH_RATE = 60; -constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); +constexpr u64 frame_ticks = static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); NVFlinger::NVFlinger() { // Add the different displays to the list of displays. @@ -48,7 +48,7 @@ NVFlinger::~NVFlinger() { } u64 NVFlinger::OpenDisplay(const std::string& name) { - LOG_WARNING(Service, "Opening display %s", name.c_str()); + NGLOG_WARNING(Service, "Opening display {}", name); // TODO(Subv): Currently we only support the Default display. ASSERT(name == "Default"); @@ -128,6 +128,8 @@ void NVFlinger::Compose() { // Search for a queued buffer and acquire it auto buffer = buffer_queue->AcquireBuffer(); + MicroProfileFlip(); + if (buffer == boost::none) { // There was no queued buffer to draw, render previous frame Core::System::GetInstance().perf_stats.EndGameFrame(); @@ -150,6 +152,9 @@ void NVFlinger::Compose() { igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform); buffer_queue->ReleaseBuffer(buffer->slot); + + // TODO(Subv): Figure out when we should actually signal this event. + buffer_queue->GetNativeHandle()->Signal(); } } @@ -159,5 +164,4 @@ Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event"); } -} // namespace NVFlinger -} // namespace Service +} // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 3126018ad..2c908297b 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -12,8 +12,7 @@ namespace CoreTiming { struct EventType; } -namespace Service { -namespace NVFlinger { +namespace Service::NVFlinger { class BufferQueue; @@ -80,5 +79,4 @@ private: CoreTiming::EventType* composition_event; }; -} // namespace NVFlinger -} // namespace Service +} // namespace Service::NVFlinger diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp new file mode 100644 index 000000000..dd20d5ae7 --- /dev/null +++ b/src/core/hle/service/pctl/module.cpp @@ -0,0 +1,146 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/pctl/module.h" +#include "core/hle/service/pctl/pctl.h" + +namespace Service::PCTL { + +class IParentalControlService final : public ServiceFramework<IParentalControlService> { +public: + IParentalControlService() : ServiceFramework("IParentalControlService") { + static const FunctionInfo functions[] = { + {1, &IParentalControlService::Initialize, "Initialize"}, + {1001, nullptr, "CheckFreeCommunicationPermission"}, + {1002, nullptr, "ConfirmLaunchApplicationPermission"}, + {1003, nullptr, "ConfirmResumeApplicationPermission"}, + {1004, nullptr, "ConfirmSnsPostPermission"}, + {1005, nullptr, "ConfirmSystemSettingsPermission"}, + {1006, nullptr, "IsRestrictionTemporaryUnlocked"}, + {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, + {1008, nullptr, "EnterRestrictedSystemSettings"}, + {1009, nullptr, "LeaveRestrictedSystemSettings"}, + {1010, nullptr, "IsRestrictedSystemSettingsEntered"}, + {1011, nullptr, "RevertRestrictedSystemSettingsEntered"}, + {1012, nullptr, "GetRestrictedFeatures"}, + {1013, nullptr, "ConfirmStereoVisionPermission"}, + {1014, nullptr, "ConfirmPlayableApplicationVideoOld"}, + {1015, nullptr, "ConfirmPlayableApplicationVideo"}, + {1031, nullptr, "IsRestrictionEnabled"}, + {1032, nullptr, "GetSafetyLevel"}, + {1033, nullptr, "SetSafetyLevel"}, + {1034, nullptr, "GetSafetyLevelSettings"}, + {1035, nullptr, "GetCurrentSettings"}, + {1036, nullptr, "SetCustomSafetyLevelSettings"}, + {1037, nullptr, "GetDefaultRatingOrganization"}, + {1038, nullptr, "SetDefaultRatingOrganization"}, + {1039, nullptr, "GetFreeCommunicationApplicationListCount"}, + {1042, nullptr, "AddToFreeCommunicationApplicationList"}, + {1043, nullptr, "DeleteSettings"}, + {1044, nullptr, "GetFreeCommunicationApplicationList"}, + {1045, nullptr, "UpdateFreeCommunicationApplicationList"}, + {1046, nullptr, "DisableFeaturesForReset"}, + {1047, nullptr, "NotifyApplicationDownloadStarted"}, + {1061, nullptr, "ConfirmStereoVisionRestrictionConfigurable"}, + {1062, nullptr, "GetStereoVisionRestriction"}, + {1063, nullptr, "SetStereoVisionRestriction"}, + {1064, nullptr, "ResetConfirmedStereoVisionPermission"}, + {1065, nullptr, "IsStereoVisionPermitted"}, + {1201, nullptr, "UnlockRestrictionTemporarily"}, + {1202, nullptr, "UnlockSystemSettingsRestriction"}, + {1203, nullptr, "SetPinCode"}, + {1204, nullptr, "GenerateInquiryCode"}, + {1205, nullptr, "CheckMasterKey"}, + {1206, nullptr, "GetPinCodeLength"}, + {1207, nullptr, "GetPinCodeChangedEvent"}, + {1208, nullptr, "GetPinCode"}, + {1403, nullptr, "IsPairingActive"}, + {1406, nullptr, "GetSettingsLastUpdated"}, + {1411, nullptr, "GetPairingAccountInfo"}, + {1421, nullptr, "GetAccountNickname"}, + {1424, nullptr, "GetAccountState"}, + {1432, nullptr, "GetSynchronizationEvent"}, + {1451, nullptr, "StartPlayTimer"}, + {1452, nullptr, "StopPlayTimer"}, + {1453, nullptr, "IsPlayTimerEnabled"}, + {1454, nullptr, "GetPlayTimerRemainingTime"}, + {1455, nullptr, "IsRestrictedByPlayTimer"}, + {1456, nullptr, "GetPlayTimerSettings"}, + {1457, nullptr, "GetPlayTimerEventToRequestSuspension"}, + {1458, nullptr, "IsPlayTimerAlarmDisabled"}, + {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, + {1472, nullptr, "CancelNetworkRequest"}, + {1473, nullptr, "GetUnlinkedEvent"}, + {1474, nullptr, "ClearUnlinkedEvent"}, + {1601, nullptr, "DisableAllFeatures"}, + {1602, nullptr, "PostEnableAllFeatures"}, + {1603, nullptr, "IsAllFeaturesDisabled"}, + {1901, nullptr, "DeleteFromFreeCommunicationApplicationListForDebug"}, + {1902, nullptr, "ClearFreeCommunicationApplicationListForDebug"}, + {1903, nullptr, "GetExemptApplicationListCountForDebug"}, + {1904, nullptr, "GetExemptApplicationListForDebug"}, + {1905, nullptr, "UpdateExemptApplicationListForDebug"}, + {1906, nullptr, "AddToExemptApplicationListForDebug"}, + {1907, nullptr, "DeleteFromExemptApplicationListForDebug"}, + {1908, nullptr, "ClearExemptApplicationListForDebug"}, + {1941, nullptr, "DeletePairing"}, + {1951, nullptr, "SetPlayTimerSettingsForDebug"}, + {1952, nullptr, "GetPlayTimerSpentTimeForTest"}, + {1953, nullptr, "SetPlayTimerAlarmDisabledForDebug"}, + {2001, nullptr, "RequestPairingAsync"}, + {2002, nullptr, "FinishRequestPairing"}, + {2003, nullptr, "AuthorizePairingAsync"}, + {2004, nullptr, "FinishAuthorizePairing"}, + {2005, nullptr, "RetrievePairingInfoAsync"}, + {2006, nullptr, "FinishRetrievePairingInfo"}, + {2007, nullptr, "UnlinkPairingAsync"}, + {2008, nullptr, "FinishUnlinkPairing"}, + {2009, nullptr, "GetAccountMiiImageAsync"}, + {2010, nullptr, "FinishGetAccountMiiImage"}, + {2011, nullptr, "GetAccountMiiImageContentTypeAsync"}, + {2012, nullptr, "FinishGetAccountMiiImageContentType"}, + {2013, nullptr, "SynchronizeParentalControlSettingsAsync"}, + {2014, nullptr, "FinishSynchronizeParentalControlSettings"}, + {2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"}, + {2016, nullptr, "RequestUpdateExemptionListAsync"}, + }; + RegisterHandlers(functions); + } + +private: + void Initialize(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 0}; + rb.Push(RESULT_SUCCESS); + } +}; + +void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IParentalControlService>(); + NGLOG_DEBUG(Service_PCTL, "called"); +} + +void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IParentalControlService>(); + NGLOG_DEBUG(Service_PCTL, "called"); +} + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<PCTL>(module, "pctl")->InstallAsService(service_manager); + std::make_shared<PCTL>(module, "pctl:a")->InstallAsService(service_manager); + std::make_shared<PCTL>(module, "pctl:r")->InstallAsService(service_manager); + std::make_shared<PCTL>(module, "pctl:s")->InstallAsService(service_manager); +} + +} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h new file mode 100644 index 000000000..68da628a8 --- /dev/null +++ b/src/core/hle/service/pctl/module.h @@ -0,0 +1,28 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::PCTL { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void CreateService(Kernel::HLERequestContext& ctx); + void CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +/// Registers all PCTL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp index 692b27a71..de2741d66 100644 --- a/src/core/hle/service/pctl/pctl.cpp +++ b/src/core/hle/service/pctl/pctl.cpp @@ -3,14 +3,15 @@ // Refer to the license.txt file included. #include "core/hle/service/pctl/pctl.h" -#include "core/hle/service/pctl/pctl_a.h" -namespace Service { -namespace PCTL { +namespace Service::PCTL { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<PCTL_A>()->InstallAsService(service_manager); +PCTL::PCTL(std::shared_ptr<Module> module, const char* name) + : Module::Interface(std::move(module), name) { + static const FunctionInfo functions[] = { + {0, &PCTL::CreateService, "CreateService"}, + {1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"}, + }; + RegisterHandlers(functions); } - -} // namespace PCTL -} // namespace Service +} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index 5fa67dd1b..8ddf69128 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -4,13 +4,13 @@ #pragma once -#include "core/hle/service/service.h" +#include "core/hle/service/pctl/module.h" -namespace Service { -namespace PCTL { +namespace Service::PCTL { -/// Registers all PCTL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +class PCTL final : public Module::Interface { +public: + explicit PCTL(std::shared_ptr<Module> module, const char* name); +}; -} // namespace PCTL -} // namespace Service +} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl_a.cpp b/src/core/hle/service/pctl/pctl_a.cpp deleted file mode 100644 index c65fffa07..000000000 --- a/src/core/hle/service/pctl/pctl_a.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/pctl/pctl_a.h" - -namespace Service { -namespace PCTL { - -class IParentalControlService final : public ServiceFramework<IParentalControlService> { -public: - IParentalControlService() : ServiceFramework("IParentalControlService") {} -}; - -void PCTL_A::GetService(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IParentalControlService>(); - LOG_DEBUG(Service_PCTL, "called"); -} - -PCTL_A::PCTL_A() : ServiceFramework("pctl:a") { - static const FunctionInfo functions[] = { - {0, &PCTL_A::GetService, "GetService"}, - }; - RegisterHandlers(functions); -} - -} // namespace PCTL -} // namespace Service diff --git a/src/core/hle/service/pctl/pctl_a.h b/src/core/hle/service/pctl/pctl_a.h deleted file mode 100644 index a89c8d07d..000000000 --- a/src/core/hle/service/pctl/pctl_a.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/hle/service/service.h" - -namespace Service { -namespace PCTL { - -class PCTL_A final : public ServiceFramework<PCTL_A> { -public: - PCTL_A(); - ~PCTL_A() = default; - -private: - void GetService(Kernel::HLERequestContext& ctx); -}; - -} // namespace PCTL -} // namespace Service diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp new file mode 100644 index 000000000..eaf30ee6b --- /dev/null +++ b/src/core/hle/service/prepo/prepo.cpp @@ -0,0 +1,43 @@ +#include <cinttypes> +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/prepo/prepo.h" + +namespace Service::PlayReport { +PlayReport::PlayReport(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {10100, nullptr, "SaveReport"}, + {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10200, nullptr, "RequestImmediateTransmission"}, + {10300, nullptr, "GetTransmissionStatus"}, + {20100, nullptr, "SaveSystemReport"}, + {20200, nullptr, "SetOperationMode"}, + {20101, nullptr, "SaveSystemReportWithUser"}, + {30100, nullptr, "ClearStorage"}, + {40100, nullptr, "IsUserAgreementCheckEnabled"}, + {40101, nullptr, "SetUserAgreementCheckEnabled"}, + {90100, nullptr, "GetStorageUsage"}, + {90200, nullptr, "GetStatistics"}, + {90201, nullptr, "GetThroughputHistory"}, + {90300, nullptr, "GetLastUploadError"}, + }; + RegisterHandlers(functions); +}; + +void PlayReport::SaveReportWithUser(Kernel::HLERequestContext& ctx) { + // TODO(ogniK): Do we want to add play report? + NGLOG_WARNING(Service_PREPO, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +}; + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager); +} + +} // namespace Service::PlayReport diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h new file mode 100644 index 000000000..3708e0dcb --- /dev/null +++ b/src/core/hle/service/prepo/prepo.h @@ -0,0 +1,23 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <string> +#include "core/hle/kernel/event.h" +#include "core/hle/service/service.h" + +namespace Service::PlayReport { + +class PlayReport final : public ServiceFramework<PlayReport> { +public: + explicit PlayReport(const char* name); + ~PlayReport() = default; + +private: + void SaveReportWithUser(Kernel::HLERequestContext& ctx); +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::PlayReport diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 6a2d6a4ef..bdd9eb5a5 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" @@ -19,19 +20,26 @@ #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/audio/audio.h" +#include "core/hle/service/bcat/bcat.h" +#include "core/hle/service/fatal/fatal.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/friend/friend.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/lm/lm.h" +#include "core/hle/service/mm/mm_u.h" +#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/pctl/pctl.h" +#include "core/hle/service/prepo/prepo.h" #include "core/hle/service/service.h" -#include "core/hle/service/set/set.h" +#include "core/hle/service/set/settings.h" #include "core/hle/service/sm/controller.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/spl/module.h" +#include "core/hle/service/ssl/ssl.h" #include "core/hle/service/time/time.h" #include "core/hle/service/vi/vi.h" @@ -52,10 +60,9 @@ static std::string MakeFunctionString(const char* name, const char* port_name, // Number of params == bits 0-5 + bits 6-11 int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); - std::string function_string = - Common::StringFromFormat("function '%s': port=%s", name, port_name); + std::string function_string = fmt::format("function '{}': port={}", name, port_name); for (int i = 1; i <= num_params; ++i) { - function_string += Common::StringFromFormat(", cmd_buff[%i]=0x%X", i, cmd_buff[i]); + function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]); } return function_string; } @@ -107,15 +114,15 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext auto cmd_buf = ctx.CommandBuffer(); std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name; - fmt::MemoryWriter w; - w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name, - cmd_buf[0]); + fmt::memory_buffer buf; + fmt::format_to(buf, "function '{}': port='{}' cmd_buf={{[0]=0x{:X}", function_name, + service_name, cmd_buf[0]); for (int i = 1; i <= 8; ++i) { - w.write(", [{}]={:#x}", i, cmd_buf[i]); + fmt::format_to(buf, ", [{}]=0x{:X}", i, cmd_buf[i]); } - w << '}'; + buf.push_back('}'); - LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); + NGLOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf)); UNIMPLEMENTED(); } @@ -126,8 +133,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { return ReportUnimplementedFunction(ctx, info); } - LOG_TRACE( - Service, "%s", + NGLOG_TRACE( + Service, "{}", MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str()); handler_invoker(this, info->handler_callback, ctx); } @@ -139,21 +146,21 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co rb.Push(RESULT_SUCCESS); return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead); } + case IPC::CommandType::ControlWithContext: case IPC::CommandType::Control: { - SM::g_service_manager->InvokeControlRequest(context); + Core::System::GetInstance().ServiceManager().InvokeControlRequest(context); break; } + case IPC::CommandType::RequestWithContext: case IPC::CommandType::Request: { InvokeRequest(context); break; } default: - UNIMPLEMENTED_MSG("command_type=%d", context.GetCommandType()); + UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType())); } - u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress()); - context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, - Kernel::g_handle_table); + context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread()); return RESULT_SUCCESS; } @@ -167,39 +174,44 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) { } /// Initialize ServiceManager -void Init() { +void Init(std::shared_ptr<SM::ServiceManager>& sm) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(); - SM::g_service_manager = std::make_shared<SM::ServiceManager>(); - SM::ServiceManager::InstallInterfaces(SM::g_service_manager); - - Account::InstallInterfaces(*SM::g_service_manager); - AM::InstallInterfaces(*SM::g_service_manager, nv_flinger); - AOC::InstallInterfaces(*SM::g_service_manager); - APM::InstallInterfaces(*SM::g_service_manager); - Audio::InstallInterfaces(*SM::g_service_manager); - FileSystem::InstallInterfaces(*SM::g_service_manager); - Friend::InstallInterfaces(*SM::g_service_manager); - HID::InstallInterfaces(*SM::g_service_manager); - LM::InstallInterfaces(*SM::g_service_manager); - NIFM::InstallInterfaces(*SM::g_service_manager); - NS::InstallInterfaces(*SM::g_service_manager); - Nvidia::InstallInterfaces(*SM::g_service_manager); - PCTL::InstallInterfaces(*SM::g_service_manager); - Sockets::InstallInterfaces(*SM::g_service_manager); - Time::InstallInterfaces(*SM::g_service_manager); - VI::InstallInterfaces(*SM::g_service_manager, nv_flinger); - Set::InstallInterfaces(*SM::g_service_manager); - - LOG_DEBUG(Service, "initialized OK"); + SM::ServiceManager::InstallInterfaces(sm); + + Account::InstallInterfaces(*sm); + AM::InstallInterfaces(*sm, nv_flinger); + AOC::InstallInterfaces(*sm); + APM::InstallInterfaces(*sm); + BCAT::InstallInterfaces(*sm); + Audio::InstallInterfaces(*sm); + Fatal::InstallInterfaces(*sm); + FileSystem::InstallInterfaces(*sm); + Friend::InstallInterfaces(*sm); + HID::InstallInterfaces(*sm); + LM::InstallInterfaces(*sm); + MM::InstallInterfaces(*sm); + NFP::InstallInterfaces(*sm); + NIFM::InstallInterfaces(*sm); + NS::InstallInterfaces(*sm); + Nvidia::InstallInterfaces(*sm); + PCTL::InstallInterfaces(*sm); + PlayReport::InstallInterfaces(*sm); + Sockets::InstallInterfaces(*sm); + SPL::InstallInterfaces(*sm); + SSL::InstallInterfaces(*sm); + Time::InstallInterfaces(*sm); + VI::InstallInterfaces(*sm, nv_flinger); + Set::InstallInterfaces(*sm); + + NGLOG_DEBUG(Service, "initialized OK"); } /// Shutdown ServiceManager void Shutdown() { - SM::g_service_manager = nullptr; g_kernel_named_ports.clear(); - LOG_DEBUG(Service, "shutdown OK"); + NGLOG_DEBUG(Service, "shutdown OK"); } } // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 9c2e826da..fee841d46 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -178,7 +178,7 @@ private: }; /// Initialize ServiceManager -void Init(); +void Init(std::shared_ptr<SM::ServiceManager>& sm); /// Shutdown ServiceManager void Shutdown(); diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 3001ee411..f0572bed6 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -9,33 +9,53 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/service/set/set.h" -namespace Service { -namespace Set { +namespace Service::Set { void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u32 id = rp.Pop<u32>(); - constexpr std::array<u8, 13> lang_codes{}; - - ctx.WriteBuffer(lang_codes.data(), lang_codes.size()); - - IPC::ResponseBuilder rb{ctx, 2}; + static constexpr std::array<LanguageCode, 17> available_language_codes = {{ + LanguageCode::JA, + LanguageCode::EN_US, + LanguageCode::FR, + LanguageCode::DE, + LanguageCode::IT, + LanguageCode::ES, + LanguageCode::ZH_CN, + LanguageCode::KO, + LanguageCode::NL, + LanguageCode::PT, + LanguageCode::RU, + LanguageCode::ZH_TW, + LanguageCode::EN_GB, + LanguageCode::FR_CA, + LanguageCode::ES_419, + LanguageCode::ZH_HANS, + LanguageCode::ZH_HANT, + }}; + ctx.WriteBuffer(available_language_codes.data(), available_language_codes.size()); + + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(available_language_codes.size())); - LOG_WARNING(Service_SET, "(STUBBED) called"); + NGLOG_DEBUG(Service_SET, "called"); } -SET::SET(const char* name) : ServiceFramework(name) { +SET::SET() : ServiceFramework("set") { static const FunctionInfo functions[] = { + {0, nullptr, "GetLanguageCode"}, {1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"}, + {2, nullptr, "MakeLanguageCode"}, + {3, nullptr, "GetAvailableLanguageCodeCount"}, + {4, nullptr, "GetRegionCode"}, + {5, nullptr, "GetAvailableLanguageCodes2"}, + {6, nullptr, "GetAvailableLanguageCodeCount2"}, + {7, nullptr, "GetKeyCodeMap"}, + {8, nullptr, "GetQuestFlag"}, }; RegisterHandlers(functions); } -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<SET>("set")->InstallAsService(service_manager); -} - -} // namespace Set -} // namespace Service +} // namespace Service::Set diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 61e957946..ec0df0152 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -6,20 +6,36 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Set { +namespace Service::Set { + +/// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. +enum class LanguageCode : u64 { + JA = 0x000000000000616A, + EN_US = 0x00000053552D6E65, + FR = 0x0000000000007266, + DE = 0x0000000000006564, + IT = 0x0000000000007469, + ES = 0x0000000000007365, + ZH_CN = 0x0000004E432D687A, + KO = 0x0000000000006F6B, + NL = 0x0000000000006C6E, + PT = 0x0000000000007470, + RU = 0x0000000000007572, + ZH_TW = 0x00000057542D687A, + EN_GB = 0x00000042472D6E65, + FR_CA = 0x00000041432D7266, + ES_419 = 0x00003931342D7365, + ZH_HANS = 0x00736E61482D687A, + ZH_HANT = 0x00746E61482D687A, +}; class SET final : public ServiceFramework<SET> { public: - explicit SET(const char* name); + explicit SET(); ~SET() = default; private: void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx); }; -/// Registers all Set services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); - -} // namespace Set -} // namespace Service +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp new file mode 100644 index 000000000..7066ef725 --- /dev/null +++ b/src/core/hle/service/set/set_cal.cpp @@ -0,0 +1,47 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set_cal.h" + +namespace Service::Set { + +SET_CAL::SET_CAL() : ServiceFramework("set:cal") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetBluetoothBdAddress"}, + {1, nullptr, "GetConfigurationId1"}, + {2, nullptr, "GetAccelerometerOffset"}, + {3, nullptr, "GetAccelerometerScale"}, + {4, nullptr, "GetGyroscopeOffset"}, + {5, nullptr, "GetGyroscopeScale"}, + {6, nullptr, "GetWirelessLanMacAddress"}, + {7, nullptr, "GetWirelessLanCountryCodeCount"}, + {8, nullptr, "GetWirelessLanCountryCodes"}, + {9, nullptr, "GetSerialNumber"}, + {10, nullptr, "SetInitialSystemAppletProgramId"}, + {11, nullptr, "SetOverlayDispProgramId"}, + {12, nullptr, "GetBatteryLot"}, + {14, nullptr, "GetEciDeviceCertificate"}, + {15, nullptr, "GetEticketDeviceCertificate"}, + {16, nullptr, "GetSslKey"}, + {17, nullptr, "GetSslCertificate"}, + {18, nullptr, "GetGameCardKey"}, + {19, nullptr, "GetGameCardCertificate"}, + {20, nullptr, "GetEciDeviceKey"}, + {21, nullptr, "GetEticketDeviceKey"}, + {22, nullptr, "GetSpeakerParameter"}, + {23, nullptr, "GetLcdVendorId"}, + {24, nullptr, "GetEciDeviceCertificate2"}, + {25, nullptr, "GetEciDeviceKey2"}, + {26, nullptr, "GetAmiiboKey"}, + {27, nullptr, "GetAmiiboEcqvCertificate"}, + {28, nullptr, "GetAmiiboEcdsaCertificate"}, + {29, nullptr, "GetAmiiboEcqvBlsKey"}, + {30, nullptr, "GetAmiiboEcqvBlsCertificate"}, + {31, nullptr, "GetAmiiboEcqvBlsRootCertificate"}, + {32, nullptr, "GetUnknownId"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h new file mode 100644 index 000000000..bb50336aa --- /dev/null +++ b/src/core/hle/service/set/set_cal.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Set { + +class SET_CAL final : public ServiceFramework<SET_CAL> { +public: + explicit SET_CAL(); + ~SET_CAL() = default; +}; + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp new file mode 100644 index 000000000..c9f938716 --- /dev/null +++ b/src/core/hle/service/set/set_fd.cpp @@ -0,0 +1,23 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set_fd.h" + +namespace Service::Set { + +SET_FD::SET_FD() : ServiceFramework("set:fd") { + static const FunctionInfo functions[] = { + {2, nullptr, "SetSettingsItemValue"}, + {3, nullptr, "ResetSettingsItemValue"}, + {4, nullptr, "CreateSettingsItemKeyIterator"}, + {10, nullptr, "ReadSettings"}, + {11, nullptr, "ResetSettings"}, + {20, nullptr, "SetWebInspectorFlag"}, + {21, nullptr, "SetAllowedSslHosts"}, + {22, nullptr, "SetHostFsMountPoint"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h new file mode 100644 index 000000000..dbd850bc7 --- /dev/null +++ b/src/core/hle/service/set/set_fd.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Set { + +class SET_FD final : public ServiceFramework<SET_FD> { +public: + explicit SET_FD(); + ~SET_FD() = default; +}; + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp new file mode 100644 index 000000000..762a664c5 --- /dev/null +++ b/src/core/hle/service/set/set_sys.cpp @@ -0,0 +1,175 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/service/set/set_sys.h" + +namespace Service::Set { + +void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) { + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + + NGLOG_WARNING(Service_SET, "(STUBBED) called"); +} + +SET_SYS::SET_SYS() : ServiceFramework("set:sys") { + static const FunctionInfo functions[] = { + {0, nullptr, "SetLanguageCode"}, + {1, nullptr, "SetNetworkSettings"}, + {2, nullptr, "GetNetworkSettings"}, + {3, nullptr, "GetFirmwareVersion"}, + {4, nullptr, "GetFirmwareVersion2"}, + {5, nullptr, "GetFirmwareVersionDigest"}, + {7, nullptr, "GetLockScreenFlag"}, + {8, nullptr, "SetLockScreenFlag"}, + {9, nullptr, "GetBacklightSettings"}, + {10, nullptr, "SetBacklightSettings"}, + {11, nullptr, "SetBluetoothDevicesSettings"}, + {12, nullptr, "GetBluetoothDevicesSettings"}, + {13, nullptr, "GetExternalSteadyClockSourceId"}, + {14, nullptr, "SetExternalSteadyClockSourceId"}, + {15, nullptr, "GetUserSystemClockContext"}, + {16, nullptr, "SetUserSystemClockContext"}, + {17, nullptr, "GetAccountSettings"}, + {18, nullptr, "SetAccountSettings"}, + {19, nullptr, "GetAudioVolume"}, + {20, nullptr, "SetAudioVolume"}, + {21, nullptr, "GetEulaVersions"}, + {22, nullptr, "SetEulaVersions"}, + {23, &SET_SYS::GetColorSetId, "GetColorSetId"}, + {24, nullptr, "SetColorSetId"}, + {25, nullptr, "GetConsoleInformationUploadFlag"}, + {26, nullptr, "SetConsoleInformationUploadFlag"}, + {27, nullptr, "GetAutomaticApplicationDownloadFlag"}, + {28, nullptr, "SetAutomaticApplicationDownloadFlag"}, + {29, nullptr, "GetNotificationSettings"}, + {30, nullptr, "SetNotificationSettings"}, + {31, nullptr, "GetAccountNotificationSettings"}, + {32, nullptr, "SetAccountNotificationSettings"}, + {35, nullptr, "GetVibrationMasterVolume"}, + {36, nullptr, "SetVibrationMasterVolume"}, + {37, nullptr, "GetSettingsItemValueSize"}, + {38, nullptr, "GetSettingsItemValue"}, + {39, nullptr, "GetTvSettings"}, + {40, nullptr, "SetTvSettings"}, + {41, nullptr, "GetEdid"}, + {42, nullptr, "SetEdid"}, + {43, nullptr, "GetAudioOutputMode"}, + {44, nullptr, "SetAudioOutputMode"}, + {45, nullptr, "IsForceMuteOnHeadphoneRemoved"}, + {46, nullptr, "SetForceMuteOnHeadphoneRemoved"}, + {47, nullptr, "GetQuestFlag"}, + {48, nullptr, "SetQuestFlag"}, + {49, nullptr, "GetDataDeletionSettings"}, + {50, nullptr, "SetDataDeletionSettings"}, + {51, nullptr, "GetInitialSystemAppletProgramId"}, + {52, nullptr, "GetOverlayDispProgramId"}, + {53, nullptr, "GetDeviceTimeZoneLocationName"}, + {54, nullptr, "SetDeviceTimeZoneLocationName"}, + {55, nullptr, "GetWirelessCertificationFileSize"}, + {56, nullptr, "GetWirelessCertificationFile"}, + {57, nullptr, "SetRegionCode"}, + {58, nullptr, "GetNetworkSystemClockContext"}, + {59, nullptr, "SetNetworkSystemClockContext"}, + {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"}, + {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"}, + {62, nullptr, "GetDebugModeFlag"}, + {63, nullptr, "GetPrimaryAlbumStorage"}, + {64, nullptr, "SetPrimaryAlbumStorage"}, + {65, nullptr, "GetUsb30EnableFlag"}, + {66, nullptr, "SetUsb30EnableFlag"}, + {67, nullptr, "GetBatteryLot"}, + {68, nullptr, "GetSerialNumber"}, + {69, nullptr, "GetNfcEnableFlag"}, + {70, nullptr, "SetNfcEnableFlag"}, + {71, nullptr, "GetSleepSettings"}, + {72, nullptr, "SetSleepSettings"}, + {73, nullptr, "GetWirelessLanEnableFlag"}, + {74, nullptr, "SetWirelessLanEnableFlag"}, + {75, nullptr, "GetInitialLaunchSettings"}, + {76, nullptr, "SetInitialLaunchSettings"}, + {77, nullptr, "GetDeviceNickName"}, + {78, nullptr, "SetDeviceNickName"}, + {79, nullptr, "GetProductModel"}, + {80, nullptr, "GetLdnChannel"}, + {81, nullptr, "SetLdnChannel"}, + {82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"}, + {83, nullptr, "GetTelemetryDirtyFlags"}, + {84, nullptr, "GetPtmBatteryLot"}, + {85, nullptr, "SetPtmBatteryLot"}, + {86, nullptr, "GetPtmFuelGaugeParameter"}, + {87, nullptr, "SetPtmFuelGaugeParameter"}, + {88, nullptr, "GetBluetoothEnableFlag"}, + {89, nullptr, "SetBluetoothEnableFlag"}, + {90, nullptr, "GetMiiAuthorId"}, + {91, nullptr, "SetShutdownRtcValue"}, + {92, nullptr, "GetShutdownRtcValue"}, + {93, nullptr, "AcquireFatalDirtyFlagEventHandle"}, + {94, nullptr, "GetFatalDirtyFlags"}, + {95, nullptr, "GetAutoUpdateEnableFlag"}, + {96, nullptr, "SetAutoUpdateEnableFlag"}, + {97, nullptr, "GetNxControllerSettings"}, + {98, nullptr, "SetNxControllerSettings"}, + {99, nullptr, "GetBatteryPercentageFlag"}, + {100, nullptr, "SetBatteryPercentageFlag"}, + {101, nullptr, "GetExternalRtcResetFlag"}, + {102, nullptr, "SetExternalRtcResetFlag"}, + {103, nullptr, "GetUsbFullKeyEnableFlag"}, + {104, nullptr, "SetUsbFullKeyEnableFlag"}, + {105, nullptr, "SetExternalSteadyClockInternalOffset"}, + {106, nullptr, "GetExternalSteadyClockInternalOffset"}, + {107, nullptr, "GetBacklightSettingsEx"}, + {108, nullptr, "SetBacklightSettingsEx"}, + {109, nullptr, "GetHeadphoneVolumeWarningCount"}, + {110, nullptr, "SetHeadphoneVolumeWarningCount"}, + {111, nullptr, "GetBluetoothAfhEnableFlag"}, + {112, nullptr, "SetBluetoothAfhEnableFlag"}, + {113, nullptr, "GetBluetoothBoostEnableFlag"}, + {114, nullptr, "SetBluetoothBoostEnableFlag"}, + {115, nullptr, "GetInRepairProcessEnableFlag"}, + {116, nullptr, "SetInRepairProcessEnableFlag"}, + {117, nullptr, "GetHeadphoneVolumeUpdateFlag"}, + {118, nullptr, "SetHeadphoneVolumeUpdateFlag"}, + {119, nullptr, "NeedsToUpdateHeadphoneVolume"}, + {120, nullptr, "GetPushNotificationActivityModeOnSleep"}, + {121, nullptr, "SetPushNotificationActivityModeOnSleep"}, + {122, nullptr, "GetServiceDiscoveryControlSettings"}, + {123, nullptr, "SetServiceDiscoveryControlSettings"}, + {124, nullptr, "GetErrorReportSharePermission"}, + {125, nullptr, "SetErrorReportSharePermission"}, + {126, nullptr, "GetAppletLaunchFlags"}, + {127, nullptr, "SetAppletLaunchFlags"}, + {128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"}, + {129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"}, + {130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"}, + {131, nullptr, "SetConsoleSixAxisSensorAngularVelocityBias"}, + {132, nullptr, "GetConsoleSixAxisSensorAccelerationGain"}, + {133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"}, + {134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"}, + {135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"}, + {136, nullptr, "GetKeyboardLayout"}, + {137, nullptr, "SetKeyboardLayout"}, + {138, nullptr, "GetWebInspectorFlag"}, + {139, nullptr, "GetAllowedSslHosts"}, + {140, nullptr, "GetHostFsMountPoint"}, + {141, nullptr, "GetRequiresRunRepairTimeReviser"}, + {142, nullptr, "SetRequiresRunRepairTimeReviser"}, + {143, nullptr, "SetBlePairingSettings"}, + {144, nullptr, "GetBlePairingSettings"}, + {145, nullptr, "GetConsoleSixAxisSensorAngularVelocityTimeBias"}, + {146, nullptr, "SetConsoleSixAxisSensorAngularVelocityTimeBias"}, + {147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"}, + {148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"}, + {149, nullptr, "GetRebootlessSystemUpdateVersion"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h new file mode 100644 index 000000000..b77a97cde --- /dev/null +++ b/src/core/hle/service/set/set_sys.h @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Set { + +class SET_SYS final : public ServiceFramework<SET_SYS> { +public: + explicit SET_SYS(); + ~SET_SYS() = default; + +private: + void GetColorSetId(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::Set diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp new file mode 100644 index 000000000..cf5541ca8 --- /dev/null +++ b/src/core/hle/service/set/settings.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set.h" +#include "core/hle/service/set/set_cal.h" +#include "core/hle/service/set/set_fd.h" +#include "core/hle/service/set/set_sys.h" +#include "core/hle/service/set/settings.h" + +namespace Service::Set { + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<SET>()->InstallAsService(service_manager); + std::make_shared<SET_CAL>()->InstallAsService(service_manager); + std::make_shared<SET_FD>()->InstallAsService(service_manager); + std::make_shared<SET_SYS>()->InstallAsService(service_manager); +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h new file mode 100644 index 000000000..6606ce776 --- /dev/null +++ b/src/core/hle/service/set/settings.h @@ -0,0 +1,14 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Set { + +/// Registers all Settings services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::Set diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index e12c53442..fe5097cdc 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -7,8 +7,7 @@ #include "core/hle/kernel/session.h" #include "core/hle/service/sm/controller.h" -namespace Service { -namespace SM { +namespace Service::SM { void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain"); @@ -18,7 +17,7 @@ void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); // Converted sessions start with 1 request handler - LOG_DEBUG(Service, "called, server_session=%d", ctx.Session()->GetObjectId()); + NGLOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetObjectId()); } void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) { @@ -30,11 +29,11 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) { Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client}; rb.PushMoveObjects(session); - LOG_DEBUG(Service, "called, session=%u", session->GetObjectId()); + NGLOG_DEBUG(Service, "called, session={}", session->GetObjectId()); } void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called, using DuplicateSession"); + NGLOG_WARNING(Service, "(STUBBED) called, using DuplicateSession"); DuplicateSession(ctx); } @@ -44,7 +43,7 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push<u32>(0x500); - LOG_WARNING(Service, "(STUBBED) called"); + NGLOG_WARNING(Service, "(STUBBED) called"); } Controller::Controller() : ServiceFramework("IpcController") { @@ -58,5 +57,4 @@ Controller::Controller() : ServiceFramework("IpcController") { RegisterHandlers(functions); } -} // namespace SM -} // namespace Service +} // namespace Service::SM diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h index 7b4bc4b75..a4de52cd2 100644 --- a/src/core/hle/service/sm/controller.h +++ b/src/core/hle/service/sm/controller.h @@ -6,8 +6,7 @@ #include "core/hle/service/service.h" -namespace Service { -namespace SM { +namespace Service::SM { class Controller final : public ServiceFramework<Controller> { public: @@ -21,5 +20,4 @@ private: void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); }; -} // namespace SM -} // namespace Service +} // namespace Service::SM diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index bc72512a0..bded8421f 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -12,8 +12,9 @@ #include "core/hle/service/sm/controller.h" #include "core/hle/service/sm/sm.h" -namespace Service { -namespace SM { +namespace Service::SM { + +ServiceManager::~ServiceManager() = default; void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { controller_interface->InvokeRequest(context); @@ -73,7 +74,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToSer return client_port->Connect(); } -std::shared_ptr<ServiceManager> g_service_manager; +SM::~SM() = default; /** * SM::Initialize service function @@ -85,7 +86,7 @@ std::shared_ptr<ServiceManager> g_service_manager; void SM::Initialize(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_SM, "called"); + NGLOG_DEBUG(Service_SM, "called"); } void SM::GetService(Kernel::HLERequestContext& ctx) { @@ -101,8 +102,8 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { if (client_port.Failed()) { IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(client_port.Code()); - LOG_ERROR(Service_SM, "called service=%s -> error 0x%08X", name.c_str(), - client_port.Code().raw); + NGLOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, + client_port.Code().raw); if (name.length() == 0) return; // LibNX Fix UNIMPLEMENTED(); @@ -112,8 +113,7 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { auto session = client_port.Unwrap()->Connect(); ASSERT(session.Succeeded()); if (session.Succeeded()) { - LOG_DEBUG(Service_SM, "called service=%s -> session=%u", name.c_str(), - (*session)->GetObjectId()); + NGLOG_DEBUG(Service_SM, "called service={} -> session={}", name, (*session)->GetObjectId()); IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles); rb.Push(session.Code()); @@ -132,5 +132,4 @@ SM::SM(std::shared_ptr<ServiceManager> service_manager) RegisterHandlers(functions); } -} // namespace SM -} // namespace Service +} // namespace Service::SM diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 11fa788ca..13f5c4c28 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -17,14 +17,13 @@ class ServerPort; class SessionRequestHandler; } // namespace Kernel -namespace Service { -namespace SM { +namespace Service::SM { /// Interface to "sm:" service class SM final : public ServiceFramework<SM> { public: SM(std::shared_ptr<ServiceManager> service_manager); - ~SM() = default; + ~SM() override; private: void Initialize(Kernel::HLERequestContext& ctx); @@ -45,6 +44,8 @@ class ServiceManager { public: static void InstallInterfaces(std::shared_ptr<ServiceManager> self); + ~ServiceManager(); + ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name, unsigned int max_sessions); ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); @@ -60,7 +61,4 @@ private: std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services; }; -extern std::shared_ptr<ServiceManager> g_service_manager; - -} // namespace SM -} // namespace Service +} // namespace Service::SM diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp new file mode 100644 index 000000000..ab909fdaa --- /dev/null +++ b/src/core/hle/service/sockets/bsd.cpp @@ -0,0 +1,114 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/sockets/bsd.h" + +namespace Service::Sockets { + +void BSD::RegisterClient(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // bsd errno +} + +void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // bsd errno +} + +void BSD::Socket(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + u32 domain = rp.Pop<u32>(); + u32 type = rp.Pop<u32>(); + u32 protocol = rp.Pop<u32>(); + + NGLOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, + protocol); + + u32 fd = next_fd++; + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(fd); + rb.Push<u32>(0); // bsd errno +} + +void BSD::Connect(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // ret + rb.Push<u32>(0); // bsd errno +} + +void BSD::SendTo(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // ret + rb.Push<u32>(0); // bsd errno +} + +void BSD::Close(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // ret + rb.Push<u32>(0); // bsd errno +} + +BSD::BSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {0, &BSD::RegisterClient, "RegisterClient"}, + {1, &BSD::StartMonitoring, "StartMonitoring"}, + {2, &BSD::Socket, "Socket"}, + {3, nullptr, "SocketExempt"}, + {4, nullptr, "Open"}, + {5, nullptr, "Select"}, + {6, nullptr, "Poll"}, + {7, nullptr, "Sysctl"}, + {8, nullptr, "Recv"}, + {9, nullptr, "RecvFrom"}, + {10, nullptr, "Send"}, + {11, &BSD::SendTo, "SendTo"}, + {12, nullptr, "Accept"}, + {13, nullptr, "Bind"}, + {14, &BSD::Connect, "Connect"}, + {15, nullptr, "GetPeerName"}, + {16, nullptr, "GetSockName"}, + {17, nullptr, "GetSockOpt"}, + {18, nullptr, "Listen"}, + {19, nullptr, "Ioctl"}, + {20, nullptr, "Fcntl"}, + {21, nullptr, "SetSockOpt"}, + {22, nullptr, "Shutdown"}, + {23, nullptr, "ShutdownAllSockets"}, + {24, nullptr, "Write"}, + {25, nullptr, "Read"}, + {26, &BSD::Close, "Close"}, + {27, nullptr, "DuplicateSocket"}, + {28, nullptr, "GetResourceStatistics"}, + {29, nullptr, "RecvMMsg"}, + {30, nullptr, "SendMMsg"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd_u.h b/src/core/hle/service/sockets/bsd.h index 4e1252e9d..a6b1ca7d0 100644 --- a/src/core/hle/service/sockets/bsd_u.h +++ b/src/core/hle/service/sockets/bsd.h @@ -7,13 +7,12 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" -namespace Service { -namespace Sockets { +namespace Service::Sockets { -class BSD_U final : public ServiceFramework<BSD_U> { +class BSD final : public ServiceFramework<BSD> { public: - BSD_U(); - ~BSD_U() = default; + explicit BSD(const char* name); + ~BSD() = default; private: void RegisterClient(Kernel::HLERequestContext& ctx); @@ -27,5 +26,4 @@ private: u32 next_fd = 1; }; -} // namespace Sockets -} // namespace Service +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd_u.cpp b/src/core/hle/service/sockets/bsd_u.cpp deleted file mode 100644 index 2ca1000ca..000000000 --- a/src/core/hle/service/sockets/bsd_u.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/sockets/bsd_u.h" - -namespace Service { -namespace Sockets { - -void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // bsd errno -} - -void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // bsd errno -} - -void BSD_U::Socket(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - u32 domain = rp.Pop<u32>(); - u32 type = rp.Pop<u32>(); - u32 protocol = rp.Pop<u32>(); - - LOG_WARNING(Service, "(STUBBED) called domain=%u type=%u protocol=%u", domain, type, protocol); - - u32 fd = next_fd++; - - IPC::ResponseBuilder rb{ctx, 4}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(fd); - rb.Push<u32>(0); // bsd errno -} - -void BSD_U::Connect(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 4}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno -} - -void BSD_U::SendTo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 4}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno -} - -void BSD_U::Close(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 4}; - - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno -} - -BSD_U::BSD_U() : ServiceFramework("bsd:u") { - static const FunctionInfo functions[] = {{0, &BSD_U::RegisterClient, "RegisterClient"}, - {1, &BSD_U::StartMonitoring, "StartMonitoring"}, - {2, &BSD_U::Socket, "Socket"}, - {11, &BSD_U::SendTo, "SendTo"}, - {14, &BSD_U::Connect, "Connect"}, - {26, &BSD_U::Close, "Close"}}; - RegisterHandlers(functions); -} - -} // namespace Sockets -} // namespace Service diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp new file mode 100644 index 000000000..8682dc2e0 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.cpp @@ -0,0 +1,32 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/sockets/nsd.h" + +namespace Service::Sockets { + +NSD::NSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {10, nullptr, "GetSettingName"}, + {11, nullptr, "GetEnvironmentIdentifier"}, + {12, nullptr, "GetDeviceId"}, + {13, nullptr, "DeleteSettings"}, + {14, nullptr, "ImportSettings"}, + {20, nullptr, "Resolve"}, + {21, nullptr, "ResolveEx"}, + {30, nullptr, "GetNasServiceSetting"}, + {31, nullptr, "GetNasServiceSettingEx"}, + {40, nullptr, "GetNasRequestFqdn"}, + {41, nullptr, "GetNasRequestFqdnEx"}, + {42, nullptr, "GetNasApiFqdn"}, + {43, nullptr, "GetNasApiFqdnEx"}, + {50, nullptr, "GetCurrentSetting"}, + {60, nullptr, "ReadSaveDataFromFsForTest"}, + {61, nullptr, "WriteSaveDataToFsForTest"}, + {62, nullptr, "DeleteSaveDataOfFsForTest"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h new file mode 100644 index 000000000..3b7edfc43 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service::Sockets { + +class NSD final : public ServiceFramework<NSD> { +public: + explicit NSD(const char* name); + ~NSD() = default; +}; + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 4d7bc7c3e..f377e59f2 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -5,13 +5,12 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/service/sockets/sfdnsres.h" -namespace Service { -namespace Sockets { +namespace Service::Sockets { void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - LOG_WARNING(Service, "(STUBBED) called"); + NGLOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; @@ -19,18 +18,20 @@ void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) { } SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { - static const FunctionInfo functions[] = {{0, nullptr, "SetDnsAddressesPrivate"}, - {1, nullptr, "GetDnsAddressPrivate"}, - {2, nullptr, "GetHostByName"}, - {3, nullptr, "GetHostByAddr"}, - {4, nullptr, "GetHostStringError"}, - {5, nullptr, "GetGaiStringError"}, - {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"}, - {7, nullptr, "GetNameInfo"}, - {8, nullptr, "RequestCancelHandle"}, - {9, nullptr, "CancelSocketCall"}}; + static const FunctionInfo functions[] = { + {0, nullptr, "SetDnsAddressesPrivate"}, + {1, nullptr, "GetDnsAddressPrivate"}, + {2, nullptr, "GetHostByName"}, + {3, nullptr, "GetHostByAddr"}, + {4, nullptr, "GetHostStringError"}, + {5, nullptr, "GetGaiStringError"}, + {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"}, + {7, nullptr, "GetNameInfo"}, + {8, nullptr, "RequestCancelHandle"}, + {9, nullptr, "CancelSocketCall"}, + {11, nullptr, "ClearDnsIpServerAddressArray"}, + }; RegisterHandlers(functions); } -} // namespace Sockets -} // namespace Service +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index b726a30fd..62c7e35bf 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -7,17 +7,15 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" -namespace Service { -namespace Sockets { +namespace Service::Sockets { class SFDNSRES final : public ServiceFramework<SFDNSRES> { public: - SFDNSRES(); + explicit SFDNSRES(); ~SFDNSRES() = default; private: void GetAddrInfo(Kernel::HLERequestContext& ctx); }; -} // namespace Sockets -} // namespace Service +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index f1396eaa1..05bd10d35 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -2,17 +2,19 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/sockets/bsd_u.h" +#include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/sockets/nsd.h" #include "core/hle/service/sockets/sfdnsres.h" #include "core/hle/service/sockets/sockets.h" -namespace Service { -namespace Sockets { +namespace Service::Sockets { void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<BSD_U>()->InstallAsService(service_manager); + std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager); + std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager); std::make_shared<SFDNSRES>()->InstallAsService(service_manager); } -} // namespace Sockets -} // namespace Service +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 7e89c8d2c..ca8a6a7e0 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -6,11 +6,9 @@ #include "core/hle/service/service.h" -namespace Service { -namespace Sockets { +namespace Service::Sockets { /// Registers all Sockets services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace Sockets -} // namespace Service +} // namespace Service::Sockets diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp new file mode 100644 index 000000000..b9e6b799d --- /dev/null +++ b/src/core/hle/service/spl/csrng.cpp @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/spl/csrng.h" + +namespace Service::SPL { + +CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") { + static const FunctionInfo functions[] = { + {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h new file mode 100644 index 000000000..3f849b5a7 --- /dev/null +++ b/src/core/hle/service/spl/csrng.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/spl/module.h" + +namespace Service::SPL { + +class CSRNG final : public Module::Interface { +public: + explicit CSRNG(std::shared_ptr<Module> module); +}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp new file mode 100644 index 000000000..76ba97156 --- /dev/null +++ b/src/core/hle/service/spl/module.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstdlib> +#include <vector> +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/spl/csrng.h" +#include "core/hle/service/spl/module.h" +#include "core/hle/service/spl/spl.h" + +namespace Service::SPL { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + size_t size = ctx.GetWriteBufferSize(); + + std::vector<u8> data(size); + std::generate(data.begin(), data.end(), std::rand); + + ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + NGLOG_DEBUG(Service_SPL, "called"); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<CSRNG>(module)->InstallAsService(service_manager); + std::make_shared<SPL>(module)->InstallAsService(service_manager); +} + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h new file mode 100644 index 000000000..6ab91b400 --- /dev/null +++ b/src/core/hle/service/spl/module.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::SPL { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void GetRandomBytes(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +/// Registers all SPL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp new file mode 100644 index 000000000..bb1e03342 --- /dev/null +++ b/src/core/hle/service/spl/spl.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/spl/spl.h" + +namespace Service::SPL { + +SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetConfig"}, + {1, nullptr, "UserExpMod"}, + {2, nullptr, "GenerateAesKek"}, + {3, nullptr, "LoadAesKey"}, + {4, nullptr, "GenerateAesKey"}, + {5, nullptr, "SetConfig"}, + {7, &SPL::GetRandomBytes, "GetRandomBytes"}, + {9, nullptr, "LoadSecureExpModKey"}, + {10, nullptr, "SecureExpMod"}, + {11, nullptr, "IsDevelopment"}, + {12, nullptr, "GenerateSpecificAesKey"}, + {13, nullptr, "DecryptPrivk"}, + {14, nullptr, "DecryptAesKey"}, + {15, nullptr, "DecryptAesCtr"}, + {16, nullptr, "ComputeCmac"}, + {17, nullptr, "LoadRsaOaepKey"}, + {18, nullptr, "UnwrapRsaOaepWrappedTitleKey"}, + {19, nullptr, "LoadTitleKey"}, + {20, nullptr, "UnwrapAesWrappedTitleKey"}, + {21, nullptr, "LockAesEngine"}, + {22, nullptr, "UnlockAesEngine"}, + {23, nullptr, "GetSplWaitEvent"}, + {24, nullptr, "SetSharedData"}, + {25, nullptr, "GetSharedData"}, + {26, nullptr, "ImportSslRsaKey"}, + {27, nullptr, "SecureExpModWithSslKey"}, + {28, nullptr, "ImportEsRsaKey"}, + {29, nullptr, "SecureExpModWithEsKey"}, + {30, nullptr, "EncryptManuRsaKeyForImport"}, + {31, nullptr, "GetPackage2Hash"}, + }; + RegisterHandlers(functions); +} + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h new file mode 100644 index 000000000..69c4c1747 --- /dev/null +++ b/src/core/hle/service/spl/spl.h @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/spl/module.h" + +namespace Service::SPL { + +class SPL final : public Module::Interface { +public: + explicit SPL(std::shared_ptr<Module> module); +}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp new file mode 100644 index 000000000..b3dad8b06 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.cpp @@ -0,0 +1,119 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/ssl/ssl.h" + +namespace Service::SSL { + +class ISslConnection final : public ServiceFramework<ISslConnection> { +public: + ISslConnection() : ServiceFramework("ISslConnection") { + static const FunctionInfo functions[] = { + {0, nullptr, "SetSocketDescriptor"}, + {1, nullptr, "SetHostName"}, + {2, nullptr, "SetVerifyOption"}, + {3, nullptr, "SetIoMode"}, + {4, nullptr, "GetSocketDescriptor"}, + {5, nullptr, "GetHostName"}, + {6, nullptr, "GetVerifyOption"}, + {7, nullptr, "GetIoMode"}, + {8, nullptr, "DoHandshake"}, + {9, nullptr, "DoHandshakeGetServerCert"}, + {10, nullptr, "Read"}, + {11, nullptr, "Write"}, + {12, nullptr, "Pending"}, + {13, nullptr, "Peek"}, + {14, nullptr, "Poll"}, + {15, nullptr, "GetVerifyCertError"}, + {16, nullptr, "GetNeededServerCertBufferSize"}, + {17, nullptr, "SetSessionCacheMode"}, + {18, nullptr, "GetSessionCacheMode"}, + {19, nullptr, "FlushSessionCache"}, + {20, nullptr, "SetRenegotiationMode"}, + {21, nullptr, "GetRenegotiationMode"}, + {22, nullptr, "SetOption"}, + {23, nullptr, "GetOption"}, + {24, nullptr, "GetVerifyCertErrors"}, + {25, nullptr, "GetCipherInfo"}, + }; + RegisterHandlers(functions); + } +}; + +class ISslContext final : public ServiceFramework<ISslContext> { +public: + ISslContext() : ServiceFramework("ISslContext") { + static const FunctionInfo functions[] = { + {0, &ISslContext::SetOption, "SetOption"}, + {1, nullptr, "GetOption"}, + {2, &ISslContext::CreateConnection, "CreateConnection"}, + {3, nullptr, "GetConnectionCount"}, + {4, nullptr, "ImportServerPki"}, + {5, nullptr, "ImportClientPki"}, + {6, nullptr, "RemoveServerPki"}, + {7, nullptr, "RemoveClientPki"}, + {8, nullptr, "RegisterInternalPki"}, + {9, nullptr, "AddPolicyOid"}, + {10, nullptr, "ImportCrl"}, + {11, nullptr, "RemoveCrl"}, + }; + RegisterHandlers(functions); + } + ~ISslContext() = default; + +private: + void SetOption(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_SSL, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } + + void CreateConnection(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_SSL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISslConnection>(); + } +}; + +void SSL::CreateContext(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_SSL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISslContext>(); +} + +SSL::SSL() : ServiceFramework("ssl") { + static const FunctionInfo functions[] = { + {0, &SSL::CreateContext, "CreateContext"}, + {1, nullptr, "GetContextCount"}, + {2, nullptr, "GetCertificates"}, + {3, nullptr, "GetCertificateBufSize"}, + {4, nullptr, "DebugIoctl"}, + {5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"}, + {6, nullptr, "FlushSessionCache"}, + }; + RegisterHandlers(functions); +} + +void SSL::SetInterfaceVersion(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_SSL, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u32 unk1 = rp.Pop<u32>(); // Probably minor/major? + u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<SSL>()->InstallAsService(service_manager); +} + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h new file mode 100644 index 000000000..8fef13022 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.h @@ -0,0 +1,24 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::SSL { + +class SSL final : public ServiceFramework<SSL> { +public: + explicit SSL(); + ~SSL() = default; + +private: + void CreateContext(Kernel::HLERequestContext& ctx); + void SetInterfaceVersion(Kernel::HLERequestContext& ctx); +}; + +/// Registers all SSL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Service::SSL diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index ad49f4265..654012189 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <chrono> +#include <ctime> #include "common/logging/log.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" @@ -12,15 +13,18 @@ #include "core/hle/service/time/time_s.h" #include "core/hle/service/time/time_u.h" -namespace Service { -namespace Time { +namespace Service::Time { class ISystemClock final : public ServiceFramework<ISystemClock> { public: ISystemClock() : ServiceFramework("ISystemClock") { static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, - {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}}; + {1, nullptr, "SetCurrentTime"}, + {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}, + {3, nullptr, "SetSystemClockContext"}, + + }; RegisterHandlers(functions); } @@ -29,14 +33,14 @@ private: const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()) .count()}; - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(time_since_epoch); } void GetSystemClockContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + NGLOG_WARNING(Service_Time, "(STUBBED) called"); SystemClockContext system_clock_ontext{}; IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; rb.Push(RESULT_SUCCESS); @@ -55,8 +59,9 @@ public: private: void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Time, "called"); - SteadyClockTimePoint steady_clock_time_point{cyclesToMs(CoreTiming::GetTicks()) / 1000}; + NGLOG_DEBUG(Service_Time, "called"); + SteadyClockTimePoint steady_clock_time_point{ + CoreTiming::cyclesToMs(CoreTiming::GetTicks()) / 1000}; IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(steady_clock_time_point); @@ -73,7 +78,7 @@ public: {3, nullptr, "LoadLocationNameList"}, {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, {5, nullptr, "GetTimeZoneRuleVersion"}, - {100, nullptr, "ToCalendarTime"}, + {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, {200, nullptr, "ToPosixTime"}, {201, nullptr, "ToPosixTimeWithMyRule"}, @@ -82,75 +87,124 @@ public: } private: + LocationName location_name{"UTC"}; + TimeZoneRule my_time_zone_rule{}; + void GetDeviceLocationName(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - LocationName location_name{}; + NGLOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(location_name); } void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + NGLOG_WARNING(Service_Time, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); } void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + NGLOG_WARNING(Service_Time, "(STUBBED) called"); + + ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule)); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + void ToCalendarTime(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 posix_time = rp.Pop<u64>(); + + NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); + + TimeZoneRule time_zone_rule{}; + auto buffer = ctx.ReadBuffer(); + std::memcpy(&time_zone_rule, buffer.data(), buffer.size()); + + CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; + CalendarAdditionalInfo additional_info{}; + + PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_time); + rb.PushRaw(additional_info); + } + void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u64 posix_time = rp.Pop<u64>(); + const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x%016llX", posix_time); + NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; CalendarAdditionalInfo additional_info{}; + + PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule); + IPC::ResponseBuilder rb{ctx, 10}; rb.Push(RESULT_SUCCESS); rb.PushRaw(calendar_time); rb.PushRaw(additional_info); } + + void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time, + CalendarAdditionalInfo& additional_info, const TimeZoneRule& /*rule*/) { + std::time_t t(posix_time); + std::tm* tm = std::localtime(&t); + if (!tm) { + return; + } + calendar_time.year = tm->tm_year + 1900; + calendar_time.month = tm->tm_mon + 1; + calendar_time.day = tm->tm_mday; + calendar_time.hour = tm->tm_hour; + calendar_time.minute = tm->tm_min; + calendar_time.second = tm->tm_sec; + + additional_info.day_of_week = tm->tm_wday; + additional_info.day_of_year = tm->tm_yday; + std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC")); + additional_info.utc_offset = 0; + } }; void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISteadyClock>(); - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ITimeZoneService>(); - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); + NGLOG_DEBUG(Service_Time, "called"); } Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) @@ -162,5 +216,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<TIME_U>(time)->InstallAsService(service_manager); } -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index 197029e7a..49af38589 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -4,14 +4,13 @@ #pragma once +#include <array> #include "core/hle/service/service.h" -namespace Service { -namespace Time { +namespace Service::Time { -// TODO(Rozelette) RE this structure struct LocationName { - INSERT_PADDING_BYTES(0x24); + std::array<u8, 0x24> name; }; static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size"); @@ -26,26 +25,34 @@ struct CalendarTime { }; static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size"); -// TODO(Rozelette) RE this structure struct CalendarAdditionalInfo { - INSERT_PADDING_BYTES(0x18); + u32_le day_of_week; + u32_le day_of_year; + std::array<u8, 8> name; + INSERT_PADDING_BYTES(1); + s32_le utc_offset; }; static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo structure has incorrect size"); -// TODO(bunnei) RE this structure -struct SystemClockContext { - INSERT_PADDING_BYTES(0x20); +// TODO(mailwl) RE this structure +struct TimeZoneRule { + INSERT_PADDING_BYTES(0x4000); }; -static_assert(sizeof(SystemClockContext) == 0x20, - "SystemClockContext structure has incorrect size"); struct SteadyClockTimePoint { - u64 value; + u64_le value; INSERT_PADDING_WORDS(4); }; static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); +struct SystemClockContext { + u64_le offset; + SteadyClockTimePoint time_point; +}; +static_assert(sizeof(SystemClockContext) == 0x20, + "SystemClockContext structure has incorrect size"); + class Module final { public: class Interface : public ServiceFramework<Interface> { @@ -66,5 +73,4 @@ public: /// Registers all Time services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_s.cpp b/src/core/hle/service/time/time_s.cpp index b172b2bd6..0b599ea00 100644 --- a/src/core/hle/service/time/time_s.cpp +++ b/src/core/hle/service/time/time_s.cpp @@ -4,8 +4,7 @@ #include "core/hle/service/time/time_s.h" -namespace Service { -namespace Time { +namespace Service::Time { TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:s") { static const FunctionInfo functions[] = { @@ -14,9 +13,19 @@ TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time) {2, &TIME_S::GetStandardSteadyClock, "GetStandardSteadyClock"}, {3, &TIME_S::GetTimeZoneService, "GetTimeZoneService"}, {4, &TIME_S::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, + {5, nullptr, "GetEphemeralNetworkSystemClock"}, + {50, nullptr, "SetStandardSteadyClockInternalOffset"}, + {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {102, nullptr, "GetStandardUserSystemClockInitialYear"}, + {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"}, + {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, + {400, nullptr, "GetClockSnapshot"}, + {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, + {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, + {501, nullptr, "CalculateSpanBetween"}, }; RegisterHandlers(functions); } -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_s.h b/src/core/hle/service/time/time_s.h index abc2a8c5a..4a2daa513 100644 --- a/src/core/hle/service/time/time_s.h +++ b/src/core/hle/service/time/time_s.h @@ -6,13 +6,11 @@ #include "core/hle/service/time/time.h" -namespace Service { -namespace Time { +namespace Service::Time { class TIME_S final : public Module::Interface { public: explicit TIME_S(std::shared_ptr<Module> time); }; -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_u.cpp b/src/core/hle/service/time/time_u.cpp index fc1ace325..1ed42c419 100644 --- a/src/core/hle/service/time/time_u.cpp +++ b/src/core/hle/service/time/time_u.cpp @@ -4,8 +4,7 @@ #include "core/hle/service/time/time_u.h" -namespace Service { -namespace Time { +namespace Service::Time { TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:u") { static const FunctionInfo functions[] = { @@ -14,9 +13,19 @@ TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time) {2, &TIME_U::GetStandardSteadyClock, "GetStandardSteadyClock"}, {3, &TIME_U::GetTimeZoneService, "GetTimeZoneService"}, {4, &TIME_U::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, + {5, nullptr, "GetEphemeralNetworkSystemClock"}, + {50, nullptr, "SetStandardSteadyClockInternalOffset"}, + {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {102, nullptr, "GetStandardUserSystemClockInitialYear"}, + {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"}, + {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, + {400, nullptr, "GetClockSnapshot"}, + {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, + {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, + {501, nullptr, "CalculateSpanBetween"}, }; RegisterHandlers(functions); } -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_u.h b/src/core/hle/service/time/time_u.h index f99d25057..3724bcdc7 100644 --- a/src/core/hle/service/time/time_u.h +++ b/src/core/hle/service/time/time_u.h @@ -6,13 +6,11 @@ #include "core/hle/service/time/time.h" -namespace Service { -namespace Time { +namespace Service::Time { class TIME_U final : public Module::Interface { public: explicit TIME_U(std::shared_ptr<Module> time); }; -} // namespace Time -} // namespace Service +} // namespace Service::Time diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 0aa621dfe..e86556671 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -4,21 +4,24 @@ #include <algorithm> #include <array> +#include <memory> +#include <boost/optional.hpp> #include "common/alignment.h" #include "common/scope_exit.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" #include "core/hle/service/vi/vi_s.h" #include "core/hle/service/vi/vi_u.h" +#include "core/settings.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" -namespace Service { -namespace VI { +namespace Service::VI { struct DisplayInfo { char display_name[0x40]{"Default"}; @@ -146,7 +149,7 @@ private: class NativeWindow : public Parcel { public: - explicit NativeWindow(u32 id) : Parcel() { + explicit NativeWindow(u32 id) { data.id = id; } ~NativeWindow() override = default; @@ -193,7 +196,7 @@ public: class IGBPConnectResponseParcel : public Parcel { public: - explicit IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() { + explicit IGBPConnectResponseParcel(u32 width, u32 height) { data.width = width; data.height = height; } @@ -243,10 +246,6 @@ public: }; class IGBPSetPreallocatedBufferResponseParcel : public Parcel { -public: - IGBPSetPreallocatedBufferResponseParcel() : Parcel() {} - ~IGBPSetPreallocatedBufferResponseParcel() override = default; - protected: void SerializeData() override { // TODO(Subv): Find out what this means @@ -285,7 +284,7 @@ static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong class IGBPDequeueBufferResponseParcel : public Parcel { public: - explicit IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {} + explicit IGBPDequeueBufferResponseParcel(u32 slot) : slot(slot) {} ~IGBPDequeueBufferResponseParcel() override = default; protected: @@ -379,7 +378,7 @@ public: class IGBPQueueBufferResponseParcel : public Parcel { public: - explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() { + explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) { data.width = width; data.height = height; } @@ -420,7 +419,7 @@ public: class IGBPQueryResponseParcel : public Parcel { public: - explicit IGBPQueryResponseParcel(u32 value) : Parcel(), value(value) {} + explicit IGBPQueryResponseParcel(u32 value) : value(value) {} ~IGBPQueryResponseParcel() override = default; protected: @@ -471,7 +470,7 @@ private: u32 flags = rp.Pop<u32>(); auto buffer_queue = nv_flinger->GetBufferQueue(id); - LOG_DEBUG(Service_VI, "called, transaction=%x", transaction); + NGLOG_DEBUG(Service_VI, "called, transaction={:X}", static_cast<u32>(transaction)); if (transaction == TransactionId::Connect) { IGBPConnectRequestParcel request{ctx.ReadBuffer()}; @@ -486,12 +485,30 @@ private: ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::DequeueBuffer) { IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; - - u32 slot = buffer_queue->DequeueBuffer(request.data.pixel_format, request.data.width, - request.data.height); - - IGBPDequeueBufferResponseParcel response{slot}; - ctx.WriteBuffer(response.Serialize()); + const u32 width{request.data.width}; + const u32 height{request.data.height}; + boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); + + if (slot != boost::none) { + // Buffer is available + IGBPDequeueBufferResponseParcel response{*slot}; + ctx.WriteBuffer(response.Serialize()); + } else { + // Wait the current thread until a buffer becomes available + auto wait_event = ctx.SleepClientThread( + Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1, + [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, + ThreadWakeupReason reason) { + // Repeat TransactParcel DequeueBuffer when a buffer is available + auto buffer_queue = nv_flinger->GetBufferQueue(id); + boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); + IGBPDequeueBufferResponseParcel response{*slot}; + ctx.WriteBuffer(response.Serialize()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + }); + buffer_queue->SetBufferWaitEvent(std::move(wait_event)); + } } else if (transaction == TransactionId::RequestBuffer) { IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; @@ -515,7 +532,7 @@ private: IGBPQueryResponseParcel response{value}; ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::CancelBuffer) { - LOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); + NGLOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); } else { ASSERT_MSG(false, "Unimplemented"); } @@ -530,7 +547,8 @@ private: s32 addval = rp.PopRaw<s32>(); u32 type = rp.Pop<u32>(); - LOG_WARNING(Service_VI, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type); + NGLOG_WARNING(Service_VI, "(STUBBED) called id={}, addval={:08X}, type={:08X}", id, addval, + type); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -544,7 +562,7 @@ private: // TODO(Subv): Find out what this actually is. - LOG_WARNING(Service_VI, "(STUBBED) called id=%u, unknown=%08X", id, unknown); + NGLOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_queue->GetNativeHandle()); @@ -558,7 +576,48 @@ public: ISystemDisplayService() : ServiceFramework("ISystemDisplayService") { static const FunctionInfo functions[] = { {1200, nullptr, "GetZOrderCountMin"}, + {1202, nullptr, "GetZOrderCountMax"}, + {1203, nullptr, "GetDisplayLogicalResolution"}, + {1204, nullptr, "SetDisplayMagnification"}, + {2201, nullptr, "SetLayerPosition"}, + {2203, nullptr, "SetLayerSize"}, + {2204, nullptr, "GetLayerZ"}, {2205, &ISystemDisplayService::SetLayerZ, "SetLayerZ"}, + {2207, &ISystemDisplayService::SetLayerVisibility, "SetLayerVisibility"}, + {2209, nullptr, "SetLayerAlpha"}, + {2312, nullptr, "CreateStrayLayer"}, + {2400, nullptr, "OpenIndirectLayer"}, + {2401, nullptr, "CloseIndirectLayer"}, + {2402, nullptr, "FlipIndirectLayer"}, + {3000, nullptr, "ListDisplayModes"}, + {3001, nullptr, "ListDisplayRgbRanges"}, + {3002, nullptr, "ListDisplayContentTypes"}, + {3200, nullptr, "GetDisplayMode"}, + {3201, nullptr, "SetDisplayMode"}, + {3202, nullptr, "GetDisplayUnderscan"}, + {3203, nullptr, "SetDisplayUnderscan"}, + {3204, nullptr, "GetDisplayContentType"}, + {3205, nullptr, "SetDisplayContentType"}, + {3206, nullptr, "GetDisplayRgbRange"}, + {3207, nullptr, "SetDisplayRgbRange"}, + {3208, nullptr, "GetDisplayCmuMode"}, + {3209, nullptr, "SetDisplayCmuMode"}, + {3210, nullptr, "GetDisplayContrastRatio"}, + {3211, nullptr, "SetDisplayContrastRatio"}, + {3214, nullptr, "GetDisplayGamma"}, + {3215, nullptr, "SetDisplayGamma"}, + {3216, nullptr, "GetDisplayCmuLuma"}, + {3217, nullptr, "SetDisplayCmuLuma"}, + {8225, nullptr, "GetSharedBufferMemoryHandleId"}, + {8250, nullptr, "OpenSharedLayer"}, + {8251, nullptr, "CloseSharedLayer"}, + {8252, nullptr, "ConnectSharedLayer"}, + {8253, nullptr, "DisconnectSharedLayer"}, + {8254, nullptr, "AcquireSharedFrameBuffer"}, + {8255, nullptr, "PresentSharedFrameBuffer"}, + {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"}, + {8257, nullptr, "FillSharedFrameBufferColor"}, + {8258, nullptr, "CancelSharedFrameBuffer"}, }; RegisterHandlers(functions); } @@ -566,7 +625,7 @@ public: private: void SetLayerZ(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + NGLOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); u64 z_value = rp.Pop<u64>(); @@ -574,6 +633,16 @@ private: IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(RESULT_SUCCESS); } + + void SetLayerVisibility(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 layer_id = rp.Pop<u64>(); + bool visibility = rp.Pop<bool>(); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:08X}, visibility={}", layer_id, + visibility); + } }; class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { @@ -581,10 +650,72 @@ public: explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) : ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) { static const FunctionInfo functions[] = { + {200, nullptr, "AllocateProcessHeapBlock"}, + {201, nullptr, "FreeProcessHeapBlock"}, {1020, &IManagerDisplayService::CloseDisplay, "CloseDisplay"}, {1102, nullptr, "GetDisplayResolution"}, {2010, &IManagerDisplayService::CreateManagedLayer, "CreateManagedLayer"}, + {2011, nullptr, "DestroyManagedLayer"}, + {2050, nullptr, "CreateIndirectLayer"}, + {2051, nullptr, "DestroyIndirectLayer"}, + {2052, nullptr, "CreateIndirectProducerEndPoint"}, + {2053, nullptr, "DestroyIndirectProducerEndPoint"}, + {2054, nullptr, "CreateIndirectConsumerEndPoint"}, + {2055, nullptr, "DestroyIndirectConsumerEndPoint"}, + {2300, nullptr, "AcquireLayerTexturePresentingEvent"}, + {2301, nullptr, "ReleaseLayerTexturePresentingEvent"}, + {2302, nullptr, "GetDisplayHotplugEvent"}, + {2402, nullptr, "GetDisplayHotplugState"}, + {2501, nullptr, "GetCompositorErrorInfo"}, + {2601, nullptr, "GetDisplayErrorEvent"}, + {4201, nullptr, "SetDisplayAlpha"}, + {4203, nullptr, "SetDisplayLayerStack"}, + {4205, nullptr, "SetDisplayPowerState"}, + {4206, nullptr, "SetDefaultDisplay"}, {6000, &IManagerDisplayService::AddToLayerStack, "AddToLayerStack"}, + {6001, nullptr, "RemoveFromLayerStack"}, + {6002, &IManagerDisplayService::SetLayerVisibility, "SetLayerVisibility"}, + {6003, nullptr, "SetLayerConfig"}, + {6004, nullptr, "AttachLayerPresentationTracer"}, + {6005, nullptr, "DetachLayerPresentationTracer"}, + {6006, nullptr, "StartLayerPresentationRecording"}, + {6007, nullptr, "StopLayerPresentationRecording"}, + {6008, nullptr, "StartLayerPresentationFenceWait"}, + {6009, nullptr, "StopLayerPresentationFenceWait"}, + {6010, nullptr, "GetLayerPresentationAllFencesExpiredEvent"}, + {7000, nullptr, "SetContentVisibility"}, + {8000, nullptr, "SetConductorLayer"}, + {8100, nullptr, "SetIndirectProducerFlipOffset"}, + {8200, nullptr, "CreateSharedBufferStaticStorage"}, + {8201, nullptr, "CreateSharedBufferTransferMemory"}, + {8202, nullptr, "DestroySharedBuffer"}, + {8203, nullptr, "BindSharedLowLevelLayerToManagedLayer"}, + {8204, nullptr, "BindSharedLowLevelLayerToIndirectLayer"}, + {8207, nullptr, "UnbindSharedLowLevelLayer"}, + {8208, nullptr, "ConnectSharedLowLevelLayerToSharedBuffer"}, + {8209, nullptr, "DisconnectSharedLowLevelLayerFromSharedBuffer"}, + {8210, nullptr, "CreateSharedLayer"}, + {8211, nullptr, "DestroySharedLayer"}, + {8216, nullptr, "AttachSharedLayerToLowLevelLayer"}, + {8217, nullptr, "ForceDetachSharedLayerFromLowLevelLayer"}, + {8218, nullptr, "StartDetachSharedLayerFromLowLevelLayer"}, + {8219, nullptr, "FinishDetachSharedLayerFromLowLevelLayer"}, + {8220, nullptr, "GetSharedLayerDetachReadyEvent"}, + {8221, nullptr, "GetSharedLowLevelLayerSynchronizedEvent"}, + {8222, nullptr, "CheckSharedLowLevelLayerSynchronized"}, + {8223, nullptr, "RegisterSharedBufferImporterAruid"}, + {8224, nullptr, "UnregisterSharedBufferImporterAruid"}, + {8227, nullptr, "CreateSharedBufferProcessHeap"}, + {8228, nullptr, "GetSharedLayerLayerStacks"}, + {8229, nullptr, "SetSharedLayerLayerStacks"}, + {8291, nullptr, "PresentDetachedSharedFrameBufferToLowLevelLayer"}, + {8292, nullptr, "FillDetachedSharedFrameBufferColor"}, + {8293, nullptr, "GetDetachedSharedFrameBufferImage"}, + {8294, nullptr, "SetDetachedSharedFrameBufferImage"}, + {8295, nullptr, "CopyDetachedSharedFrameBufferImage"}, + {8296, nullptr, "SetDetachedSharedFrameBufferSubImage"}, + {8297, nullptr, "GetSharedFrameBufferContentParameter"}, + {8298, nullptr, "ExpandStartupLogoOnSharedFrameBuffer"}, }; RegisterHandlers(functions); } @@ -592,7 +723,7 @@ public: private: void CloseDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + NGLOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u64 display = rp.Pop<u64>(); @@ -601,7 +732,7 @@ private: } void CreateManagedLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + NGLOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u32 unknown = rp.Pop<u32>(); rp.Skip(1, false); @@ -616,7 +747,7 @@ private: } void AddToLayerStack(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + NGLOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u32 stack = rp.Pop<u32>(); u64 layer_id = rp.Pop<u64>(); @@ -625,147 +756,182 @@ private: rb.Push(RESULT_SUCCESS); } + void SetLayerVisibility(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 layer_id = rp.Pop<u64>(); + bool visibility = rp.Pop<bool>(); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + NGLOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:X}, visibility={}", layer_id, + visibility); + } + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; -void IApplicationDisplayService::GetRelayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); +class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { +public: + IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~IApplicationDisplayService() = default; - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); -} +private: + void GetRelayService(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); -void IApplicationDisplayService::GetSystemDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + } - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemDisplayService>(); -} + void GetSystemDisplayService(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); -void IApplicationDisplayService::GetManagerDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemDisplayService>(); + } - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IManagerDisplayService>(nv_flinger); -} + void GetManagerDisplayService(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); -void IApplicationDisplayService::GetIndirectDisplayTransactionService( - Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IManagerDisplayService>(nv_flinger); + } - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); -} + void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); -void IApplicationDisplayService::OpenDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); - auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + } - std::string name(name_buf.begin(), end); + void OpenDisplay(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); + auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); - ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); + std::string name(name_buf.begin(), end); - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(nv_flinger->OpenDisplay(name)); -} + ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); -void IApplicationDisplayService::CloseDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u64 display_id = rp.Pop<u64>(); + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(nv_flinger->OpenDisplay(name)); + } - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); - rb.Push(RESULT_SUCCESS); -} + void CloseDisplay(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u64 display_id = rp.Pop<u64>(); -void IApplicationDisplayService::OpenLayer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_VI, "called"); - IPC::RequestParser rp{ctx}; - auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); - auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } - std::string display_name(name_buf.begin(), end); + void GetDisplayResolution(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u64 display_id = rp.Pop<u64>(); - u64 layer_id = rp.Pop<u64>(); - u64 aruid = rp.Pop<u64>(); + IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); + rb.Push(RESULT_SUCCESS); - u64 display_id = nv_flinger->OpenDisplay(display_name); - u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); + if (Settings::values.use_docked_mode) { + rb.Push(static_cast<u64>(DisplayResolution::DockedWidth)); + rb.Push(static_cast<u64>(DisplayResolution::DockedHeight)); + } else { + rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth)); + rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight)); + } + } - NativeWindow native_window{buffer_queue_id}; - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); -} + void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u32 scaling_mode = rp.Pop<u32>(); + u64 unknown = rp.Pop<u64>(); -void IApplicationDisplayService::CreateStrayLayer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_VI, "called"); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } - IPC::RequestParser rp{ctx}; - u32 flags = rp.Pop<u32>(); - rp.Pop<u32>(); // padding - u64 display_id = rp.Pop<u64>(); + void ListDisplays(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + DisplayInfo display_info; + ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(1); + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + } - // TODO(Subv): What's the difference between a Stray and a Managed layer? + void OpenLayer(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_VI, "called"); + IPC::RequestParser rp{ctx}; + auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); + auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); - u64 layer_id = nv_flinger->CreateLayer(display_id); - u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); + std::string display_name(name_buf.begin(), end); - NativeWindow native_window{buffer_queue_id}; - IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push(layer_id); - rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); -} + u64 layer_id = rp.Pop<u64>(); + u64 aruid = rp.Pop<u64>(); -void IApplicationDisplayService::DestroyStrayLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); + u64 display_id = nv_flinger->OpenDisplay(display_name); + u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); - IPC::RequestParser rp{ctx}; - u64 layer_id = rp.Pop<u64>(); + NativeWindow native_window{buffer_queue_id}; + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); + } - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); - rb.Push(RESULT_SUCCESS); -} + void CreateStrayLayer(Kernel::HLERequestContext& ctx) { + NGLOG_DEBUG(Service_VI, "called"); -void IApplicationDisplayService::SetLayerScalingMode(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u32 scaling_mode = rp.Pop<u32>(); - u64 unknown = rp.Pop<u64>(); + IPC::RequestParser rp{ctx}; + u32 flags = rp.Pop<u32>(); + rp.Pop<u32>(); // padding + u64 display_id = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); - rb.Push(RESULT_SUCCESS); -} + // TODO(Subv): What's the difference between a Stray and a Managed layer? -void IApplicationDisplayService::ListDisplays(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - DisplayInfo display_info; - ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(1); - LOG_WARNING(Service_VI, "(STUBBED) called"); -} + u64 layer_id = nv_flinger->CreateLayer(display_id); + u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); -void IApplicationDisplayService::GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u64 display_id = rp.Pop<u64>(); + NativeWindow native_window{buffer_queue_id}; + IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(layer_id); + rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); + } - auto vsync_event = nv_flinger->GetVsyncEvent(display_id); + void DestroyStrayLayer(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0); - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(vsync_event); -} + IPC::RequestParser rp{ctx}; + u64 layer_id = rp.Pop<u64>(); + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } + + void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u64 display_id = rp.Pop<u64>(); + + auto vsync_event = nv_flinger->GetVsyncEvent(display_id); + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0); + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(vsync_event); + } + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; +}; IApplicationDisplayService::IApplicationDisplayService( std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) @@ -778,22 +944,43 @@ IApplicationDisplayService::IApplicationDisplayService( "GetIndirectDisplayTransactionService"}, {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"}, {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"}, + {1011, nullptr, "OpenDefaultDisplay"}, {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"}, - {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, + {1101, nullptr, "SetDisplayEnabled"}, + {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"}, {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"}, + {2021, nullptr, "CloseLayer"}, {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"}, {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"}, + {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, + {2102, nullptr, "ConvertScalingMode"}, + {2450, nullptr, "GetIndirectLayerImageMap"}, + {2451, nullptr, "GetIndirectLayerImageCropMap"}, + {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"}, {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"}, + {5203, nullptr, "GetDisplayVsyncEventForDebug"}, }; RegisterHandlers(functions); } +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {} + +void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) { + NGLOG_WARNING(Service_VI, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); +} + void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) { - std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager); + auto module = std::make_shared<Module>(); + std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager); } -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index f6be7d1e6..e8bda01d7 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -4,9 +4,6 @@ #pragma once -#include <memory> -#include <boost/optional.hpp> -#include "core/hle/kernel/event.h" #include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/service.h" @@ -14,34 +11,32 @@ namespace CoreTiming { struct EventType; } -namespace Service { -namespace VI { +namespace Service::VI { -class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { +enum class DisplayResolution : u32 { + DockedWidth = 1920, + DockedHeight = 1080, + UndockedWidth = 1280, + UndockedHeight = 720, +}; + +class Module final { public: - IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~IApplicationDisplayService() = default; - -private: - void GetRelayService(Kernel::HLERequestContext& ctx); - void GetSystemDisplayService(Kernel::HLERequestContext& ctx); - void GetManagerDisplayService(Kernel::HLERequestContext& ctx); - void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx); - void OpenDisplay(Kernel::HLERequestContext& ctx); - void CloseDisplay(Kernel::HLERequestContext& ctx); - void SetLayerScalingMode(Kernel::HLERequestContext& ctx); - void ListDisplays(Kernel::HLERequestContext& ctx); - void OpenLayer(Kernel::HLERequestContext& ctx); - void CreateStrayLayer(Kernel::HLERequestContext& ctx); - void DestroyStrayLayer(Kernel::HLERequestContext& ctx); - void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + + void GetDisplayService(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + }; }; /// Registers all VI services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 5d99647dc..d47da565b 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -2,24 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" -namespace Service { -namespace VI { +namespace Service::VI { -void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); -} - -VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("vi:m"), nv_flinger(std::move(nv_flinger)) { +VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -27,5 +15,4 @@ VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) RegisterHandlers(functions); } -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index e5319b1e7..6abb9b3a3 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -4,26 +4,13 @@ #pragma once -#include <memory> -#include "core/hle/service/service.h" +#include "core/hle/service/vi/vi.h" -namespace Service { -namespace NVFlinger { -class NVFlinger; -} +namespace Service::VI { -namespace VI { - -class VI_M final : public ServiceFramework<VI_M> { +class VI_M final : public Module::Interface { public: - VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~VI_M() = default; - -private: - void GetDisplayService(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); }; -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 411757981..8f82e797f 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -2,24 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_s.h" -namespace Service { -namespace VI { +namespace Service::VI { -void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); -} - -VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("vi:s"), nv_flinger(std::move(nv_flinger)) { +VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) { static const FunctionInfo functions[] = { {1, &VI_S::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -27,5 +15,4 @@ VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) RegisterHandlers(functions); } -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index 6978fd700..8f16f804f 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -4,26 +4,13 @@ #pragma once -#include <memory> -#include "core/hle/service/service.h" +#include "core/hle/service/vi/vi.h" -namespace Service { -namespace NVFlinger { -class NVFlinger; -} +namespace Service::VI { -namespace VI { - -class VI_S final : public ServiceFramework<VI_S> { +class VI_S final : public Module::Interface { public: - VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~VI_S() = default; - -private: - void GetDisplayService(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); }; -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index f5568383b..b84aed1d5 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -2,30 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_u.h" -namespace Service { -namespace VI { +namespace Service::VI { -void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); -} - -VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("vi:u"), nv_flinger(std::move(nv_flinger)) { +VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) { static const FunctionInfo functions[] = { {0, &VI_U::GetDisplayService, "GetDisplayService"}, - {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, }; RegisterHandlers(functions); } -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index b3e9c094d..e9b4f76b2 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -4,26 +4,13 @@ #pragma once -#include <memory> -#include "core/hle/service/service.h" +#include "core/hle/service/vi/vi.h" -namespace Service { -namespace NVFlinger { -class NVFlinger; -} +namespace Service::VI { -namespace VI { - -class VI_U final : public ServiceFramework<VI_U> { +class VI_U final : public Module::Interface { public: - VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~VI_U() = default; - -private: - void GetDisplayService(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); }; -} // namespace VI -} // namespace Service +} // namespace Service::VI diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index bba4a0715..9ed8ab249 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -56,13 +56,14 @@ static void UpdateTimeCallback(u64 userdata, int cycles_late) { date_time.date_time = GetSystemTime(); date_time.update_tick = CoreTiming::GetTicks(); - date_time.tick_to_second_coefficient = BASE_CLOCK_RATE; + date_time.tick_to_second_coefficient = CoreTiming::BASE_CLOCK_RATE; date_time.tick_offset = 0; ++shared_page.date_time_counter; // system time is updated hourly - CoreTiming::ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event); + CoreTiming::ScheduleEvent(CoreTiming::msToCycles(60 * 60 * 1000) - cycles_late, + update_time_event); } void Init() { diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 0db604c76..8fc91dc9c 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -33,7 +33,8 @@ inline void Read(T& var, const u32 addr) { LCD::Read(var, addr); break; default: - LOG_ERROR(HW_Memory, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); + NGLOG_ERROR(HW_Memory, "Unknown Read{} @ 0x{:08X}", sizeof(var) * 8, addr); + break; } } @@ -61,7 +62,8 @@ inline void Write(u32 addr, const T data) { LCD::Write(addr, data); break; default: - LOG_ERROR(HW_Memory, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); + NGLOG_ERROR(HW_Memory, "Unknown Write{} 0x{:08X} @ 0x{:08X}", sizeof(data) * 8, data, addr); + break; } } @@ -83,12 +85,12 @@ void Update() {} /// Initialize hardware void Init() { LCD::Init(); - LOG_DEBUG(HW, "initialized OK"); + NGLOG_DEBUG(HW, "Initialized OK"); } /// Shutdown hardware void Shutdown() { LCD::Shutdown(); - LOG_DEBUG(HW, "shutdown OK"); + NGLOG_DEBUG(HW, "Shutdown OK"); } } // namespace HW diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp index 690079b65..e8525efde 100644 --- a/src/core/hw/lcd.cpp +++ b/src/core/hw/lcd.cpp @@ -20,7 +20,7 @@ inline void Read(T& var, const u32 raw_addr) { // Reads other than u32 are untested, so I'd rather have them abort than silently fail if (index >= 0x400 || !std::is_same<T, u32>::value) { - LOG_ERROR(HW_LCD, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); + NGLOG_ERROR(HW_LCD, "Unknown Read{} @ 0x{:08X}", sizeof(var) * 8, addr); return; } @@ -34,7 +34,7 @@ inline void Write(u32 addr, const T data) { // Writes other than u32 are untested, so I'd rather have them abort than silently fail if (index >= 0x400 || !std::is_same<T, u32>::value) { - LOG_ERROR(HW_LCD, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); + NGLOG_ERROR(HW_LCD, "Unknown Write{} 0x{:08X} @ 0x{:08X}", sizeof(data) * 8, data, addr); return; } @@ -56,12 +56,12 @@ template void Write<u8>(u32 addr, const u8 data); /// Initialize hardware void Init() { memset(&g_regs, 0, sizeof(g_regs)); - LOG_DEBUG(HW_LCD, "initialized OK"); + NGLOG_DEBUG(HW_LCD, "Initialized OK"); } /// Shutdown hardware void Shutdown() { - LOG_DEBUG(HW_LCD, "shutdown OK"); + NGLOG_DEBUG(HW_LCD, "Shutdown OK"); } } // namespace LCD diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 864cf25cd..b01b2caf6 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -76,7 +76,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& fil } else if (Common::ToLower(virtual_name) == "sdk") { is_sdk_found = true; } else { - // Contrinue searching + // Continue searching return true; } @@ -110,8 +110,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( return ResultStatus::Error; } - process = Kernel::Process::Create("main"); - const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; const std::string npdm_path = directory + DIR_SEP + "main.npdm"; @@ -121,20 +119,26 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( } metadata.Print(); + const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; + if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit) { + return ResultStatus::ErrorUnsupportedArch; + } + // Load NSO modules VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { const std::string path = directory + DIR_SEP + module; const VAddr load_addr = next_load_addr; - next_load_addr = AppLoader_NSO::LoadModule(path, load_addr, metadata.GetTitleID()); + next_load_addr = AppLoader_NSO::LoadModule(path, load_addr); if (next_load_addr) { - LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, module, load_addr); + NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); } else { next_load_addr = load_addr; } } + process->program_id = metadata.GetTitleID(); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; process->resource_limit = @@ -159,7 +163,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS( std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) { if (filepath_romfs.empty()) { - LOG_DEBUG(Loader, "No RomFS available"); + NGLOG_DEBUG(Loader, "No RomFS available"); return ResultStatus::ErrorNotUsed; } @@ -172,8 +176,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS( offset = 0; size = romfs_file->GetSize(); - LOG_DEBUG(Loader, "RomFS offset: 0x%016" PRIX64, offset); - LOG_DEBUG(Loader, "RomFS size: 0x%016" PRIX64, size); + NGLOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset); + NGLOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size); // Reset read pointer file.Seek(0, SEEK_SET); diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index b87320656..e42d3a870 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -273,18 +273,18 @@ const char* ElfReader::GetSectionName(int section) const { } SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { - LOG_DEBUG(Loader, "String section: %i", header->e_shstrndx); + NGLOG_DEBUG(Loader, "String section: {}", header->e_shstrndx); // Should we relocate? relocate = (header->e_type != ET_EXEC); if (relocate) { - LOG_DEBUG(Loader, "Relocatable module"); + NGLOG_DEBUG(Loader, "Relocatable module"); entryPoint += vaddr; } else { - LOG_DEBUG(Loader, "Prerelocated executable"); + NGLOG_DEBUG(Loader, "Prerelocated executable"); } - LOG_DEBUG(Loader, "%i segments:", header->e_phnum); + NGLOG_DEBUG(Loader, "{} segments:", header->e_phnum); // First pass : Get the bits into RAM u32 base_addr = relocate ? vaddr : 0; @@ -300,12 +300,12 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { std::vector<u8> program_image(total_image_size); size_t current_image_position = 0; - SharedPtr<CodeSet> codeset = CodeSet::Create("", 0); + SharedPtr<CodeSet> codeset = CodeSet::Create(""); for (unsigned int i = 0; i < header->e_phnum; ++i) { Elf32_Phdr* p = &segments[i]; - LOG_DEBUG(Loader, "Type: %i Vaddr: %08X Filesz: %8X Memsz: %8X ", p->p_type, p->p_vaddr, - p->p_filesz, p->p_memsz); + NGLOG_DEBUG(Loader, "Type: {} Vaddr: {:08X} Filesz: {:08X} Memsz: {:08X} ", p->p_type, + p->p_vaddr, p->p_filesz, p->p_memsz); if (p->p_type == PT_LOAD) { CodeSet::Segment* codeset_segment; @@ -317,16 +317,16 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { } else if (permission_flags == (PF_R | PF_W)) { codeset_segment = &codeset->data; } else { - LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id %u with flags %X", i, - p->p_flags); + NGLOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i, + p->p_flags); continue; } if (codeset_segment->size != 0) { - LOG_ERROR(Loader, - "ELF has more than one segment of the same type. Skipping extra " - "segment (id %i)", - i); + NGLOG_ERROR(Loader, + "ELF has more than one segment of the same type. Skipping extra " + "segment (id {})", + i); continue; } @@ -345,7 +345,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { codeset->entrypoint = base_addr + header->e_entry; codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - LOG_DEBUG(Loader, "Done loading."); + NGLOG_DEBUG(Loader, "Done loading."); return codeset; } @@ -406,7 +406,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); codeset->name = filename; - process = Kernel::Process::Create("main"); process->LoadModule(codeset, codeset->entrypoint); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; @@ -415,7 +414,7 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(codeset->entrypoint, 48, Kernel::DEFAULT_STACK_SIZE); + process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp index 87cc65e91..c7be5f265 100644 --- a/src/core/loader/linker.cpp +++ b/src/core/loader/linker.cpp @@ -84,7 +84,7 @@ void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector< } break; default: - LOG_CRITICAL(Loader, "Unknown relocation type: %d", rela.type); + NGLOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type)); break; } } @@ -141,7 +141,7 @@ void Linker::ResolveImports() { if (search != exports.end()) { Memory::Write64(import.second.ea, search->second + import.second.addend); } else { - LOG_ERROR(Loader, "Unresolved import: %s", import.first.c_str()); + NGLOG_ERROR(Loader, "Unresolved import: {}", import.first); } } } diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 2ec08506d..6a4fd38cb 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -41,7 +41,7 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) { FileType IdentifyFile(const std::string& file_name) { FileUtil::IOFile file(file_name, "rb"); if (!file.IsOpen()) { - LOG_ERROR(Loader, "Failed to load file %s", file_name.c_str()); + NGLOG_ERROR(Loader, "Failed to load file {}", file_name); return FileType::Unknown; } @@ -116,7 +116,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { FileUtil::IOFile file(filename, "rb"); if (!file.IsOpen()) { - LOG_ERROR(Loader, "Failed to load file %s", filename.c_str()); + NGLOG_ERROR(Loader, "Failed to load file {}", filename); return nullptr; } @@ -127,12 +127,12 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { FileType filename_type = GuessFromExtension(filename_extension); if (type != filename_type) { - LOG_WARNING(Loader, "File %s has a different type than its extension.", filename.c_str()); + NGLOG_WARNING(Loader, "File {} has a different type than its extension.", filename); if (FileType::Unknown == type) type = filename_type; } - LOG_DEBUG(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); + NGLOG_DEBUG(Loader, "Loading file {} as {}...", filename, GetFileTypeString(type)); return GetFileLoader(std::move(file), type, filename_filename, filename); } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index dd44ee9a6..b1aabb1cb 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -72,6 +72,7 @@ enum class ResultStatus { ErrorAlreadyLoaded, ErrorMemoryAllocationFailed, ErrorEncrypted, + ErrorUnsupportedArch, }; /// Interface for loading an application diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 6f8a2f21e..3853cfa1a 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -8,6 +8,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/loader/nro.h" @@ -83,7 +84,7 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { } // Build program image - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0); + Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); std::vector<u8> program_image; program_image.resize(PageAlignSize(nro_header.file_size)); file.Seek(0, SEEK_SET); @@ -112,7 +113,7 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { // Load codeset for current process codeset->name = path; codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Kernel::g_current_process->LoadModule(codeset, load_base); + Core::CurrentProcess()->LoadModule(codeset, load_base); return true; } @@ -125,8 +126,6 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { return ResultStatus::Error; } - process = Kernel::Process::Create("main"); - // Load NRO static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR}; @@ -138,7 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(base_addr, 48, Kernel::DEFAULT_STACK_SIZE); + process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 7f8d24dd6..01be9e217 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -9,6 +9,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/loader/nso.h" @@ -72,7 +73,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade file.Seek(header.offset, SEEK_SET); if (compressed_size != file.ReadBytes(compressed_data.data(), compressed_size)) { - LOG_CRITICAL(Loader, "Failed to read %d NSO LZ4 compressed bytes", compressed_size); + NGLOG_CRITICAL(Loader, "Failed to read {} NSO LZ4 compressed bytes", compressed_size); return {}; } @@ -83,7 +84,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size); ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), - "%d != %u != %zu", bytes_uncompressed, header.size, uncompressed_data.size()); + "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); return uncompressed_data; } @@ -92,7 +93,7 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 tid) { +VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { FileUtil::IOFile file(path, "rb"); if (!file.IsOpen()) { return {}; @@ -109,7 +110,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 ti } // Build program image - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", tid); + Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); std::vector<u8> program_image; for (int i = 0; i < nso_header.segments.size(); ++i) { std::vector<u8> data = @@ -142,7 +143,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 ti // Load codeset for current process codeset->name = path; codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Kernel::g_current_process->LoadModule(codeset, load_base); + Core::CurrentProcess()->LoadModule(codeset, load_base); return load_base + image_size; } @@ -155,18 +156,15 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { return ResultStatus::Error; } - process = Kernel::Process::Create("main"); - // Load module - LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR, 0); - LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, filepath.c_str(), - Memory::PROCESS_IMAGE_VADDR); + LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR); + NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", filepath, Memory::PROCESS_IMAGE_VADDR); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Kernel::DEFAULT_STACK_SIZE); + process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 14eb1d87e..1ae30a824 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -29,7 +29,7 @@ public: return IdentifyType(file, filepath); } - static VAddr LoadModule(const std::string& path, VAddr load_base, u64 tid); + static VAddr LoadModule(const std::string& path, VAddr load_base); ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index ce62666d7..3b81acd63 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -4,7 +4,6 @@ #include <algorithm> #include <array> -#include <cinttypes> #include <cstring> #include <boost/optional.hpp> #include "common/assert.h" @@ -15,6 +14,7 @@ #include "core/core.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" +#include "core/hle/lock.h" #include "core/memory.h" #include "core/memory_setup.h" #include "video_core/renderer_base.h" @@ -23,14 +23,18 @@ namespace Memory { static std::array<u8, Memory::VRAM_SIZE> vram; -static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; static PageTable* current_page_table = nullptr; void SetCurrentPageTable(PageTable* page_table) { current_page_table = page_table; - if (Core::System::GetInstance().IsPoweredOn()) { - Core::CPU().PageTableChanged(); + + auto& system = Core::System::GetInstance(); + if (system.IsPoweredOn()) { + system.ArmInterface(0).PageTableChanged(); + system.ArmInterface(1).PageTableChanged(); + system.ArmInterface(2).PageTableChanged(); + system.ArmInterface(3).PageTableChanged(); } } @@ -39,12 +43,15 @@ PageTable* GetCurrentPageTable() { } static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) { - LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE, - (base + size) * PAGE_SIZE); + NGLOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE, + (base + size) * PAGE_SIZE); + + RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, + FlushMode::FlushAndInvalidate); VAddr end = base + size; while (base != end) { - ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base); + ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at {:016X}", base); page_table.attributes[base] = type; page_table.pointers[base] = memory; @@ -56,14 +63,14 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa } void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); } void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); @@ -72,8 +79,8 @@ void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer } void UnmapRegion(PageTable& page_table, VAddr base, u64 size) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); @@ -109,100 +116,129 @@ static std::set<MemoryHookPointer> GetSpecialHandlers(const PageTable& page_tabl } static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; + const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; return GetSpecialHandlers(page_table, vaddr, size); } -template <typename T> -boost::optional<T> ReadSpecial(VAddr addr); +/** + * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) + * using a VMA from the current process + */ +static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { + u8* direct_pointer = nullptr; + + auto& vm_manager = process.vm_manager; + + auto it = vm_manager.FindVMA(vaddr); + ASSERT(it != vm_manager.vma_map.end()); + + auto& vma = it->second; + switch (vma.type) { + case Kernel::VMAType::AllocatedMemoryBlock: + direct_pointer = vma.backing_block->data() + vma.offset; + break; + case Kernel::VMAType::BackingMemory: + direct_pointer = vma.backing_memory; + break; + case Kernel::VMAType::Free: + return nullptr; + default: + UNREACHABLE(); + } + + return direct_pointer + (vaddr - vma.base); +} + +/** + * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) + * using a VMA from the current process. + */ +static u8* GetPointerFromVMA(VAddr vaddr) { + return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); +} template <typename T> T Read(const VAddr vaddr) { - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) { - LOG_ERROR(HW_Memory, "Read%lu after page table @ 0x%016" PRIX64, sizeof(T) * 8, vaddr); - return 0; + const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + T value; + std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); + return value; } - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + + PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { case PageType::Unmapped: - LOG_ERROR(HW_Memory, "unmapped Read%zu @ 0x%016" PRIX64, sizeof(T) * 8, vaddr); + NGLOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); return 0; - case PageType::Special: { - if (auto result = ReadSpecial<T>(vaddr)) - return *result; - [[fallthrough]]; - } - case PageType::Memory: { - const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr); + case PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); + break; + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); T value; - std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); + std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); return value; } + default: + UNREACHABLE(); } - UNREACHABLE(); - return 0; } template <typename T> -bool WriteSpecial(VAddr addr, const T data); - -template <typename T> void Write(const VAddr vaddr, const T data) { - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) { - LOG_ERROR(HW_Memory, "Write%lu after page table 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8, - (u32)data, vaddr); + u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); return; } - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + + PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { case PageType::Unmapped: - LOG_ERROR(HW_Memory, "unmapped Write%zu 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return; - case PageType::Special: { - if (WriteSpecial<T>(vaddr, data)) - return; - [[fallthrough]]; - } - case PageType::Memory: { - u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr); - std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); + NGLOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, + static_cast<u32>(data), vaddr); return; + case PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); + break; + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate); + std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); + break; } + default: + UNREACHABLE(); } - UNREACHABLE(); } bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { auto& page_table = process.vm_manager.page_table; - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) - return false; + const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS]; + if (page_pointer) + return true; - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { - case PageType::Unmapped: - return false; - case PageType::Memory: + if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) return true; - case PageType::Special: { - for (auto handler : GetSpecialHandlers(page_table, vaddr, 1)) - if (auto result = handler->IsValidAddress(vaddr)) - return *result; - return current_page_table->pointers[vaddr >> PAGE_BITS] != nullptr; - } - } - UNREACHABLE(); + + if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) + return false; + return false; } bool IsValidVirtualAddress(const VAddr vaddr) { - return IsValidVirtualAddress(*Kernel::g_current_process, vaddr); + return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr); } bool IsValidPhysicalAddress(const PAddr paddr) { @@ -215,7 +251,11 @@ u8* GetPointer(const VAddr vaddr) { return page_pointer + (vaddr & PAGE_MASK); } - LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%016" PRIx64, vaddr); + if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) { + return GetPointerFromVMA(vaddr); + } + + NGLOG_ERROR(HW_Memory, "Unknown GetPointer @ 0x{:016X}", vaddr); return nullptr; } @@ -244,7 +284,6 @@ u8* GetPhysicalPointer(PAddr address) { {IO_AREA_PADDR, IO_AREA_SIZE}, {DSP_RAM_PADDR, DSP_RAM_SIZE}, {FCRAM_PADDR, FCRAM_N3DS_SIZE}, - {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE}, }; const auto area = @@ -253,13 +292,12 @@ u8* GetPhysicalPointer(PAddr address) { }); if (area == std::end(memory_areas)) { - LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%016" PRIX64, address); + NGLOG_ERROR(HW_Memory, "Unknown GetPhysicalPointer @ 0x{:016X}", address); return nullptr; } if (area->paddr_base == IO_AREA_PADDR) { - LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%016" PRIX64, - address); + NGLOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr={:016X}", address); return nullptr; } @@ -283,9 +321,6 @@ u8* GetPhysicalPointer(PAddr address) { } ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address"); break; - case N3DS_EXTRA_RAM_PADDR: - target_pointer = n3ds_extra_ram.data() + offset_into_region; - break; default: UNREACHABLE(); } @@ -293,6 +328,127 @@ u8* GetPhysicalPointer(PAddr address) { return target_pointer; } +void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached) { + if (gpu_addr == 0) { + return; + } + + // Iterate over a contiguous CPU address space, which corresponds to the specified GPU address + // space, marking the region as un/cached. The region is marked un/cached at a granularity of + // CPU pages, hence why we iterate on a CPU page basis (note: GPU page size is different). This + // assumes the specified GPU address region is contiguous as well. + + u64 num_pages = ((gpu_addr + size - 1) >> PAGE_BITS) - (gpu_addr >> PAGE_BITS) + 1; + for (unsigned i = 0; i < num_pages; ++i, gpu_addr += PAGE_SIZE) { + boost::optional<VAddr> maybe_vaddr = + Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(gpu_addr); + // The GPU <-> CPU virtual memory mapping is not 1:1 + if (!maybe_vaddr) { + NGLOG_ERROR(HW_Memory, + "Trying to flush a cached region to an invalid physical address {:016X}", + gpu_addr); + continue; + } + VAddr vaddr = *maybe_vaddr; + + PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + + if (cached) { + // Switch page type to cached if now cached + switch (page_type) { + case PageType::Unmapped: + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case PageType::Memory: + page_type = PageType::RasterizerCachedMemory; + current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; + break; + case PageType::RasterizerCachedMemory: + // There can be more than one GPU region mapped per CPU region, so it's common that + // this area is already marked as cached. + break; + default: + UNREACHABLE(); + } + } else { + // Switch page type to uncached if now uncached + switch (page_type) { + case PageType::Unmapped: + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case PageType::Memory: + // There can be more than one GPU region mapped per CPU region, so it's common that + // this area is already unmarked as cached. + break; + case PageType::RasterizerCachedMemory: { + u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); + if (pointer == nullptr) { + // It's possible that this function has been called while updating the pagetable + // after unmapping a VMA. In that case the underlying VMA will no longer exist, + // and we should just leave the pagetable entry blank. + page_type = PageType::Unmapped; + } else { + page_type = PageType::Memory; + current_page_table->pointers[vaddr >> PAGE_BITS] = pointer; + } + break; + } + default: + UNREACHABLE(); + } + } + } +} + +void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) { + // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be + // null here + if (VideoCore::g_renderer == nullptr) { + return; + } + + VAddr end = start + size; + + auto CheckRegion = [&](VAddr region_start, VAddr region_end) { + if (start >= region_end || end <= region_start) { + // No overlap with region + return; + } + + VAddr overlap_start = std::max(start, region_start); + VAddr overlap_end = std::min(end, region_end); + + std::vector<Tegra::GPUVAddr> gpu_addresses = + Core::System::GetInstance().GPU().memory_manager->CpuToGpuAddress(overlap_start); + + if (gpu_addresses.empty()) { + return; + } + + u64 overlap_size = overlap_end - overlap_start; + + for (const auto& gpu_address : gpu_addresses) { + auto* rasterizer = VideoCore::g_renderer->Rasterizer(); + switch (mode) { + case FlushMode::Flush: + rasterizer->FlushRegion(gpu_address, overlap_size); + break; + case FlushMode::Invalidate: + rasterizer->InvalidateRegion(gpu_address, overlap_size); + break; + case FlushMode::FlushAndInvalidate: + rasterizer->FlushAndInvalidateRegion(gpu_address, overlap_size); + break; + } + } + }; + + CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END); + CheckRegion(HEAP_VADDR, HEAP_VADDR_END); +} + u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -309,17 +465,6 @@ u64 Read64(const VAddr addr) { return Read<u64_le>(addr); } -static bool ReadSpecialBlock(const Kernel::Process& process, const VAddr src_addr, - void* dest_buffer, const size_t size) { - auto& page_table = process.vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, src_addr, size)) { - if (handler->ReadBlock(src_addr, dest_buffer, size)) { - return true; - } - } - return false; -} - void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer, const size_t size) { auto& page_table = process.vm_manager.page_table; @@ -329,21 +474,17 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped ReadBlock @ 0x%016" PRIX64 " (start address = 0x%" PRIx64 - ", size = %zu)", - current_vaddr, src_addr, size); + case PageType::Unmapped: { + NGLOG_ERROR(HW_Memory, + "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); break; - case PageType::Special: { - if (ReadSpecialBlock(process, current_vaddr, dest_buffer, copy_amount)) - break; - [[fallthrough]]; } case PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); @@ -352,6 +493,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ std::memcpy(dest_buffer, src_ptr, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Flush); + std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount); + break; + } default: UNREACHABLE(); } @@ -364,7 +511,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ } void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { - ReadBlock(*Kernel::g_current_process, src_addr, dest_buffer, size); + ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); } void Write8(const VAddr addr, const u8 data) { @@ -383,17 +530,6 @@ void Write64(const VAddr addr, const u64 data) { Write<u64_le>(addr, data); } -static bool WriteSpecialBlock(const Kernel::Process& process, const VAddr dest_addr, - const void* src_buffer, const size_t size) { - auto& page_table = process.vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, dest_addr, size)) { - if (handler->WriteBlock(dest_addr, src_buffer, size)) { - return true; - } - } - return false; -} - void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer, const size_t size) { auto& page_table = process.vm_manager.page_table; @@ -402,20 +538,17 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi size_t page_offset = dest_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped WriteBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", - current_vaddr, dest_addr, size); + case PageType::Unmapped: { + NGLOG_ERROR(HW_Memory, + "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + current_vaddr, dest_addr, size); break; - case PageType::Special: - if (WriteSpecialBlock(process, current_vaddr, src_buffer, copy_amount)) - break; - [[fallthrough]]; + } case PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); @@ -423,6 +556,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi std::memcpy(dest_ptr, src_buffer, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Invalidate); + std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); + break; + } default: UNREACHABLE(); } @@ -435,12 +574,11 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi } void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { - WriteBlock(*Kernel::g_current_process, dest_addr, src_buffer, size); + WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); } -void ZeroBlock(const VAddr dest_addr, const size_t size) { - const auto& process = *Kernel::g_current_process; - +void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = dest_addr >> PAGE_BITS; size_t page_offset = dest_addr & PAGE_MASK; @@ -448,27 +586,30 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { static const std::array<u8, PAGE_SIZE> zeros = {}; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped ZeroBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", - current_vaddr, dest_addr, size); + switch (page_table.attributes[page_index]) { + case PageType::Unmapped: { + NGLOG_ERROR(HW_Memory, + "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + current_vaddr, dest_addr, size); break; - case PageType::Special: - if (WriteSpecialBlock(process, current_vaddr, zeros.data(), copy_amount)) - break; - [[fallthrough]]; + } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); + DEBUG_ASSERT(page_table.pointers[page_index]); - u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; + u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memset(dest_ptr, 0, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Invalidate); + std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount); + break; + } default: UNREACHABLE(); } @@ -479,37 +620,35 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { } } -void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { - const auto& process = *Kernel::g_current_process; - +void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = src_addr >> PAGE_BITS; size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped CopyBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", - current_vaddr, src_addr, size); - ZeroBlock(dest_addr, copy_amount); + switch (page_table.attributes[page_index]) { + case PageType::Unmapped: { + NGLOG_ERROR(HW_Memory, + "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + current_vaddr, src_addr, size); + ZeroBlock(process, dest_addr, copy_amount); break; - case PageType::Special: { - std::vector<u8> buffer(copy_amount); - if (ReadSpecialBlock(process, current_vaddr, buffer.data(), buffer.size())) { - WriteBlock(dest_addr, buffer.data(), buffer.size()); - break; - } - [[fallthrough]]; } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); - const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; - WriteBlock(dest_addr, src_ptr, copy_amount); + DEBUG_ASSERT(page_table.pointers[page_index]); + const u8* src_ptr = page_table.pointers[page_index] + page_offset; + WriteBlock(process, dest_addr, src_ptr, copy_amount); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Flush); + WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount); break; } default: @@ -524,76 +663,8 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { } } -template <> -boost::optional<u8> ReadSpecial<u8>(VAddr addr) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8))) - if (auto result = handler->Read8(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u16> ReadSpecial<u16>(VAddr addr) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16))) - if (auto result = handler->Read16(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u32> ReadSpecial<u32>(VAddr addr) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32))) - if (auto result = handler->Read32(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u64> ReadSpecial<u64>(VAddr addr) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64))) - if (auto result = handler->Read64(addr)) - return *result; - return {}; -} - -template <> -bool WriteSpecial<u8>(VAddr addr, const u8 data) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8))) - if (handler->Write8(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u16>(VAddr addr, const u16 data) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16))) - if (handler->Write16(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u32>(VAddr addr, const u32 data) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32))) - if (handler->Write32(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u64>(VAddr addr, const u64 data) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64))) - if (handler->Write64(addr, data)) - return true; - return false; +void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size) { + CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); } boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { @@ -609,8 +680,6 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { return addr - DSP_RAM_VADDR + DSP_RAM_PADDR; } else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) { return addr - IO_AREA_VADDR + IO_AREA_PADDR; - } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) { - return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR; } return boost::none; @@ -619,7 +688,7 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { PAddr VirtualToPhysicalAddress(const VAddr addr) { auto paddr = TryVirtualToPhysicalAddress(addr); if (!paddr) { - LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%016" PRIX64, addr); + NGLOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:016X}", addr); // To help with debugging, set bit on address so that it's obviously invalid. return addr | 0x80000000; } @@ -632,13 +701,11 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) { } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { return addr - VRAM_PADDR + VRAM_VADDR; } else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) { - return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapAreaAddress(); + return addr - FCRAM_PADDR + Core::CurrentProcess()->GetLinearHeapAreaAddress(); } else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) { return addr - DSP_RAM_PADDR + DSP_RAM_VADDR; } else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) { return addr - IO_AREA_PADDR + IO_AREA_VADDR; - } else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) { - return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR; } return boost::none; diff --git a/src/core/memory.h b/src/core/memory.h index f3ace7a98..3f56a2c6a 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -14,6 +14,7 @@ #include <boost/optional.hpp> #include "common/common_types.h" #include "core/memory_hook.h" +#include "video_core/memory_manager.h" namespace Kernel { class Process; @@ -36,7 +37,10 @@ enum class PageType : u8 { Unmapped, /// Page is mapped to regular memory. This is the only type you can get pointers to. Memory, - /// Page is mapped to a memory hook, which intercepts read and write requests. + /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and + /// invalidation + RasterizerCachedMemory, + /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. Special, }; @@ -98,12 +102,6 @@ enum : PAddr { VRAM_SIZE = 0x00600000, ///< VRAM size (6MB) VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE, - /// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by - /// applications and system modules if mapped via the ExHeader. - N3DS_EXTRA_RAM_PADDR = 0x1F000000, - N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB) - N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE, - /// DSP memory DSP_RAM_PADDR = 0x1FF00000, DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB) @@ -119,7 +117,6 @@ enum : PAddr { FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB) FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB) FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE, - FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE, }; /// Virtual user-space memory regions @@ -129,31 +126,12 @@ enum : VAddr { PROCESS_IMAGE_MAX_SIZE = 0x08000000, PROCESS_IMAGE_VADDR_END = PROCESS_IMAGE_VADDR + PROCESS_IMAGE_MAX_SIZE, - /// Area where IPC buffers are mapped onto. - IPC_MAPPING_VADDR = 0x04000000, - IPC_MAPPING_SIZE = 0x04000000, - IPC_MAPPING_VADDR_END = IPC_MAPPING_VADDR + IPC_MAPPING_SIZE, - - /// Application heap (includes stack). - HEAP_VADDR = 0x108000000, - HEAP_SIZE = 0xF0000000, - HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE, - - /// Area where shared memory buffers are mapped onto. - SHARED_MEMORY_VADDR = 0x10000000, - SHARED_MEMORY_SIZE = 0x04000000, - SHARED_MEMORY_VADDR_END = SHARED_MEMORY_VADDR + SHARED_MEMORY_SIZE, - /// Maps 1:1 to an offset in FCRAM. Used for HW allocations that need to be linear in physical /// memory. LINEAR_HEAP_VADDR = 0x14000000, LINEAR_HEAP_SIZE = 0x08000000, LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE, - /// Maps 1:1 to New 3DS additional memory - N3DS_EXTRA_RAM_VADDR = 0x1E800000, - N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE, - /// Maps 1:1 to the IO register area. IO_AREA_VADDR = 0x1EC00000, IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE, @@ -176,14 +154,40 @@ enum : VAddr { SHARED_PAGE_SIZE = 0x00001000, SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE, - /// Area where TLS (Thread-Local Storage) buffers are allocated. - TLS_AREA_VADDR = 0x228000000, - TLS_ENTRY_SIZE = 0x200, - /// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS. NEW_LINEAR_HEAP_VADDR = 0x30000000, NEW_LINEAR_HEAP_SIZE = 0x10000000, NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, + + /// Area where TLS (Thread-Local Storage) buffers are allocated. + TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END, + TLS_ENTRY_SIZE = 0x200, + TLS_AREA_SIZE = 0x10000000, + TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, + + /// Application stack + STACK_AREA_VADDR = TLS_AREA_VADDR_END, + STACK_AREA_SIZE = 0x10000000, + STACK_AREA_VADDR_END = STACK_AREA_VADDR + STACK_AREA_SIZE, + DEFAULT_STACK_SIZE = 0x100000, + + /// Application heap + /// Size is confirmed to be a static value on fw 3.0.0 + HEAP_VADDR = 0x108000000, + HEAP_SIZE = 0x180000000, + HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE, + + /// New map region + /// Size is confirmed to be a static value on fw 3.0.0 + NEW_MAP_REGION_VADDR = HEAP_VADDR_END, + NEW_MAP_REGION_SIZE = 0x80000000, + NEW_MAP_REGION_VADDR_END = NEW_MAP_REGION_VADDR + NEW_MAP_REGION_SIZE, + + /// Map region + /// Size is confirmed to be a static value on fw 3.0.0 + MAP_REGION_VADDR = NEW_MAP_REGION_VADDR_END, + MAP_REGION_SIZE = 0x1000000000, + MAP_REGION_VADDR_END = MAP_REGION_VADDR + MAP_REGION_SIZE, }; /// Currently active page table @@ -243,4 +247,24 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr); */ u8* GetPhysicalPointer(PAddr address); +enum class FlushMode { + /// Write back modified surfaces to RAM + Flush, + /// Remove region from the cache + Invalidate, + /// Write back modified surfaces to RAM, and also remove them from the cache + FlushAndInvalidate, +}; + +/** + * Mark each page touching the region as cached. + */ +void RasterizerMarkRegionCached(Tegra::GPUVAddr start, u64 size, bool cached); + +/** + * Flushes and invalidates any externally cached rasterizer resources touching the given virtual + * address region. + */ +void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode); + } // namespace Memory diff --git a/src/core/memory_hook.cpp b/src/core/memory_hook.cpp new file mode 100644 index 000000000..c61c6c1fb --- /dev/null +++ b/src/core/memory_hook.cpp @@ -0,0 +1,11 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/memory_hook.h" + +namespace Memory { + +MemoryHook::~MemoryHook() = default; + +} // namespace Memory diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h index feebd850a..e8ea19333 100644 --- a/src/core/memory_hook.h +++ b/src/core/memory_hook.h @@ -23,7 +23,7 @@ namespace Memory { */ class MemoryHook { public: - virtual ~MemoryHook() = default; + virtual ~MemoryHook(); virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index ad3b56fcc..5f53b16d3 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <chrono> #include <mutex> #include <thread> @@ -87,7 +88,7 @@ void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) { frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us); frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime); frame_limiting_delta_err = - MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); + std::clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US); if (frame_limiting_delta_err > microseconds::zero()) { std::this_thread::sleep_for(frame_limiting_delta_err); diff --git a/src/core/settings.h b/src/core/settings.h index 6f8cd0f03..a7f1e5fa0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -99,18 +99,20 @@ enum Values { NumAnalogs, }; +constexpr int STICK_HID_BEGIN = LStick; +constexpr int STICK_HID_END = NumAnalogs; +constexpr int NUM_STICKS_HID = NumAnalogs; + static const std::array<const char*, NumAnalogs> mapping = {{ "lstick", "rstick", }}; } // namespace NativeAnalog -enum class CpuCore { - Unicorn, - Dynarmic, -}; - struct Values { + // System + bool use_docked_mode; + // Controls std::array<std::string, NativeButton::NumButtons> buttons; std::array<std::string, NativeAnalog::NumAnalogs> analogs; @@ -118,7 +120,8 @@ struct Values { std::string touch_device; // Core - CpuCore cpu_core; + bool use_cpu_jit; + bool use_multi_core; // Data Storage bool use_virtual_sd; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index bea05a09b..a60aa1143 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -42,14 +42,14 @@ u64 GetTelemetryId() { if (FileUtil::Exists(filename)) { FileUtil::IOFile file(filename, "rb"); if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str()); + NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; } file.ReadBytes(&telemetry_id, sizeof(u64)); } else { FileUtil::IOFile file(filename, "wb"); if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str()); + NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; } telemetry_id = GenerateTelemetryId(); @@ -65,7 +65,7 @@ u64 RegenerateTelemetryId() { FileUtil::IOFile file(filename, "wb"); if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str()); + NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; } file.WriteBytes(&new_telemetry_id, sizeof(u64)); @@ -87,8 +87,8 @@ TelemetrySession::TelemetrySession() { #ifdef ENABLE_WEB_SERVICE if (Settings::values.enable_telemetry) { backend = std::make_unique<WebService::TelemetryJson>( - Settings::values.telemetry_endpoint_url, Settings::values.citra_username, - Settings::values.citra_token); + Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username, + Settings::values.yuzu_token); } else { backend = std::make_unique<Telemetry::NullVisitor>(); } @@ -154,12 +154,15 @@ TelemetrySession::TelemetrySession() { #endif // Log user configuration information - AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore", - static_cast<int>(Settings::values.cpu_core)); + AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); + AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore", + Settings::values.use_multi_core); AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", Settings::values.resolution_factor); AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit", Settings::values.toggle_framelimit); + AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode", + Settings::values.use_docked_mode); } TelemetrySession::~TelemetrySession() { diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 550c6ea2d..dbc4f8bd4 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -50,8 +50,8 @@ u64 RegenerateTelemetryId(); /** * Verifies the username and token. - * @param username Citra username to use for authentication. - * @param token Citra token to use for authentication. + * @param username yuzu username to use for authentication. + * @param token yuzu token to use for authentication. * @param func A function that gets exectued when the verification is finished * @returns Future with bool indicating whether the verification succeeded */ diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index f3b0d6a8f..2f848c994 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -159,7 +159,7 @@ void Recorder::Finish(const std::string& filename) { throw "Failed to write stream element"; } } catch (const char* str) { - LOG_ERROR(HW_GPU, "Writing CiTrace file failed: %s", str); + NGLOG_ERROR(HW_GPU, "Writing CiTrace file failed: {}", str); } } diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index 59a035e70..caffe48cb 100644 --- a/src/input_common/motion_emu.cpp +++ b/src/input_common/motion_emu.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <chrono> #include <mutex> #include <thread> @@ -43,8 +44,8 @@ public: tilt_angle = 0; } else { tilt_direction = mouse_move.Cast<float>(); - tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f, - MathUtil::PI * 0.5f); + tilt_angle = + std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, MathUtil::PI * 0.5f); } } } diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index 3b87d6b65..231a0f7af 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -32,7 +32,7 @@ public: explicit SDLJoystick(int joystick_index) : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} { if (!joystick) { - LOG_ERROR(Input, "failed to open joystick %d", joystick_index); + NGLOG_ERROR(Input, "failed to open joystick {}", joystick_index); } } @@ -204,7 +204,7 @@ public: trigger_if_greater = false; } else { trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction %s", direction_name.c_str()); + NGLOG_ERROR(Input, "Unknown direction '{}'", direction_name); } return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold, trigger_if_greater); @@ -235,7 +235,7 @@ public: void Init() { if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError()); + NGLOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); } else { using namespace Input; RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 88bbbc95c..7f9f27e19 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -15,8 +15,8 @@ static Memory::PageTable* page_table = nullptr; TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { - Kernel::g_current_process = Kernel::Process::Create(""); - page_table = &Kernel::g_current_process->vm_manager.page_table; + Core::CurrentProcess() = Kernel::Process::Create(""); + page_table = &Core::CurrentProcess()->vm_manager.page_table; page_table->pointers.fill(nullptr); page_table->special_regions.clear(); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index ed87f8ff1..6e193e7e1 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,24 +1,49 @@ add_library(video_core STATIC command_processor.cpp command_processor.h + debug_utils/debug_utils.cpp + debug_utils/debug_utils.h engines/fermi_2d.cpp engines/fermi_2d.h engines/maxwell_3d.cpp engines/maxwell_3d.h engines/maxwell_compute.cpp engines/maxwell_compute.h + engines/maxwell_dma.cpp + engines/maxwell_dma.h + engines/shader_bytecode.h + gpu.cpp gpu.h + macro_interpreter.cpp + macro_interpreter.h memory_manager.cpp memory_manager.h + rasterizer_interface.h renderer_base.cpp renderer_base.h + renderer_opengl/gl_rasterizer.cpp + renderer_opengl/gl_rasterizer.h + renderer_opengl/gl_rasterizer_cache.cpp + renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_decompiler.cpp + renderer_opengl/gl_shader_decompiler.h + renderer_opengl/gl_shader_gen.cpp + renderer_opengl/gl_shader_gen.h + renderer_opengl/gl_shader_manager.cpp + renderer_opengl/gl_shader_manager.h renderer_opengl/gl_shader_util.cpp renderer_opengl/gl_shader_util.h renderer_opengl/gl_state.cpp renderer_opengl/gl_state.h + renderer_opengl/gl_stream_buffer.cpp + renderer_opengl/gl_stream_buffer.h + renderer_opengl/maxwell_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h + textures/decoders.cpp + textures/decoders.h + textures/texture.h utils.h video_core.cpp video_core.h diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 21d672085..cec9cb9f3 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -16,6 +16,7 @@ #include "video_core/engines/fermi_2d.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_compute.h" +#include "video_core/engines/maxwell_dma.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -24,24 +25,25 @@ namespace Tegra { enum class BufferMethods { BindObject = 0, - CountBufferMethods = 0x100, + CountBufferMethods = 0x40, }; -void GPU::WriteReg(u32 method, u32 subchannel, u32 value) { - LOG_WARNING(HW_GPU, "Processing method %08X on subchannel %u value %08X", method, subchannel, - value); +void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) { + NGLOG_WARNING(HW_GPU, + "Processing method {:08X} on subchannel {} value " + "{:08X} remaining params {}", + method, subchannel, value, remaining_params); if (method == static_cast<u32>(BufferMethods::BindObject)) { // Bind the current subchannel to the desired engine id. - LOG_DEBUG(HW_GPU, "Binding subchannel %u to engine %u", subchannel, value); - ASSERT(bound_engines.find(subchannel) == bound_engines.end()); + NGLOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value); bound_engines[subchannel] = static_cast<EngineID>(value); return; } if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) { // TODO(Subv): Research and implement these methods. - LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); + NGLOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); return; } @@ -54,22 +56,23 @@ void GPU::WriteReg(u32 method, u32 subchannel, u32 value) { fermi_2d->WriteReg(method, value); break; case EngineID::MAXWELL_B: - maxwell_3d->WriteReg(method, value); + maxwell_3d->WriteReg(method, value, remaining_params); break; case EngineID::MAXWELL_COMPUTE_B: maxwell_compute->WriteReg(method, value); break; + case EngineID::MAXWELL_DMA_COPY_A: + maxwell_dma->WriteReg(method, value); + break; default: - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented engine"); } } void GPU::ProcessCommandList(GPUVAddr address, u32 size) { - // TODO(Subv): PhysicalToVirtualAddress is a misnomer, it converts a GPU VAddr into an - // application VAddr. - const VAddr head_address = memory_manager->PhysicalToVirtualAddress(address); - VAddr current_addr = head_address; - while (current_addr < head_address + size * sizeof(CommandHeader)) { + const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address); + VAddr current_addr = *head_address; + while (current_addr < *head_address + size * sizeof(CommandHeader)) { const CommandHeader header = {Memory::Read32(current_addr)}; current_addr += sizeof(u32); @@ -78,7 +81,8 @@ void GPU::ProcessCommandList(GPUVAddr address, u32 size) { case SubmissionMode::Increasing: { // Increase the method value with each argument. for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr)); + WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); current_addr += sizeof(u32); } break; @@ -87,27 +91,31 @@ void GPU::ProcessCommandList(GPUVAddr address, u32 size) { case SubmissionMode::NonIncreasing: { // Use the same method value for all arguments. for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr)); + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); current_addr += sizeof(u32); } break; } case SubmissionMode::IncreaseOnce: { ASSERT(header.arg_count.Value() >= 1); + // Use the original method for the first argument and then the next method for all other // arguments. - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr)); + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - 1); current_addr += sizeof(u32); - // Use the same method value for all arguments. + for (unsigned i = 1; i < header.arg_count; ++i) { - WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr)); + WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); current_addr += sizeof(u32); } break; } case SubmissionMode::Inline: { // The register value is stored in the bits 16-28 as an immediate - WriteReg(header.method, header.subchannel, header.inline_data); + WriteReg(header.method, header.subchannel, header.inline_data, 0); break; } default: diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h index b511bfcf7..f7214ffec 100644 --- a/src/video_core/command_processor.h +++ b/src/video_core/command_processor.h @@ -34,6 +34,4 @@ static_assert(std::is_standard_layout<CommandHeader>::value == true, "CommandHeader does not use standard layout"); static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!"); -void ProcessCommandList(VAddr address, u32 size); - } // namespace Tegra diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp new file mode 100644 index 000000000..22d44aab2 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -0,0 +1,64 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <algorithm> +#include <condition_variable> +#include <cstdint> +#include <cstring> +#include <fstream> +#include <map> +#include <mutex> +#include <string> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/debug_utils/debug_utils.h" + +namespace Tegra { + +void DebugContext::DoOnEvent(Event event, void* data) { + { + std::unique_lock<std::mutex> lock(breakpoint_mutex); + + // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will + // show on debug widgets + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::lock_guard<std::mutex> lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +} // namespace Tegra diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h new file mode 100644 index 000000000..bbba8e380 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h @@ -0,0 +1,163 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> +#include <condition_variable> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Tegra { + +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + MaxwellCommandLoaded = FirstEvent, + MaxwellCommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state + * access + * @todo Evaluate an alternative interface, in which there is only one managing observer and + * multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr<DebugContext> debug_context) + : context_weak(debug_context) { + std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock<std::mutex> lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper yuzu + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellBreakPointHit(Event event, void* data) {} + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellResume() {} + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr<DebugContext> context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr<DebugContext> Construct() { + return std::shared_ptr<DebugContext>(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until + * Resume() is called. + */ + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. + * Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + for (auto& bp : breakpoints) { + bp.enabled = false; + } + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::array<BreakPoint, (int)Event::NumEvents> breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list<BreakPointObserver*> breakpoint_observers; +}; + +} // namespace Tegra diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 7aab163dc..998b7c843 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -2,12 +2,72 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/memory.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/textures/decoders.h" namespace Tegra { namespace Engines { -void Fermi2D::WriteReg(u32 method, u32 value) {} +Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} + +void Fermi2D::WriteReg(u32 method, u32 value) { + ASSERT_MSG(method < Regs::NUM_REGS, + "Invalid Fermi2D register, increase the size of the Regs structure"); + + regs.reg_array[method] = value; + + switch (method) { + case FERMI2D_REG_INDEX(trigger): { + HandleSurfaceCopy(); + break; + } + } +} + +void Fermi2D::HandleSurfaceCopy() { + NGLOG_WARNING(HW_GPU, "Requested a surface copy with operation {}", + static_cast<u32>(regs.operation)); + + const GPUVAddr source = regs.src.Address(); + const GPUVAddr dest = regs.dst.Address(); + + // TODO(Subv): Only same-format and same-size copies are allowed for now. + ASSERT(regs.src.format == regs.dst.format); + ASSERT(regs.src.width * regs.src.height == regs.dst.width * regs.dst.height); + + // TODO(Subv): Only raw copies are implemented. + ASSERT(regs.operation == Regs::Operation::SrcCopy); + + const VAddr source_cpu = *memory_manager.GpuToCpuAddress(source); + const VAddr dest_cpu = *memory_manager.GpuToCpuAddress(dest); + + u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format); + u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); + + if (regs.src.linear == regs.dst.linear) { + // If the input layout and the output layout are the same, just perform a raw copy. + ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight()); + Memory::CopyBlock(dest_cpu, source_cpu, + src_bytes_per_pixel * regs.dst.width * regs.dst.height); + return; + } + + u8* src_buffer = Memory::GetPointer(source_cpu); + u8* dst_buffer = Memory::GetPointer(dest_cpu); + + if (!regs.src.linear && regs.dst.linear) { + // If the input is tiled and the output is linear, deswizzle the input and copy it over. + Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, + dst_bytes_per_pixel, src_buffer, dst_buffer, true, + regs.src.BlockHeight()); + } else { + // If the input is linear and the output is tiled, swizzle the input and copy it over. + Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, + dst_bytes_per_pixel, dst_buffer, src_buffer, false, + regs.dst.BlockHeight()); + } +} } // namespace Engines } // namespace Tegra diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 8967ddede..70667cb94 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -4,19 +4,111 @@ #pragma once +#include <array> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" #include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" namespace Tegra { namespace Engines { +#define FERMI2D_REG_INDEX(field_name) \ + (offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32)) + class Fermi2D final { public: - Fermi2D() = default; + explicit Fermi2D(MemoryManager& memory_manager); ~Fermi2D() = default; /// Write the value to the register identified by method. void WriteReg(u32 method, u32 value); + + struct Regs { + static constexpr size_t NUM_REGS = 0x258; + + struct Surface { + RenderTargetFormat format; + BitField<0, 1, u32> linear; + union { + BitField<0, 4, u32> block_depth; + BitField<4, 4, u32> block_height; + BitField<8, 4, u32> block_width; + }; + u32 depth; + u32 layer; + u32 pitch; + u32 width; + u32 height; + u32 address_high; + u32 address_low; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + + u32 BlockHeight() const { + // The block height is stored in log2 format. + return 1 << block_height; + } + }; + static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size"); + + enum class Operation : u32 { + SrcCopyAnd = 0, + ROPAnd = 1, + Blend = 2, + SrcCopy = 3, + ROP = 4, + SrcCopyPremult = 5, + BlendPremult = 6, + }; + + union { + struct { + INSERT_PADDING_WORDS(0x80); + + Surface dst; + + INSERT_PADDING_WORDS(2); + + Surface src; + + INSERT_PADDING_WORDS(0x15); + + Operation operation; + + INSERT_PADDING_WORDS(0x9); + + // TODO(Subv): This is only a guess. + u32 trigger; + + INSERT_PADDING_WORDS(0x1A3); + }; + std::array<u32, NUM_REGS> reg_array; + }; + } regs{}; + + MemoryManager& memory_manager; + +private: + /// Performs the copy from the source surface to the destination surface as configured in the + /// registers. + void HandleSurfaceCopy(); }; +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(Fermi2D::Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(dst, 0x80); +ASSERT_REG_POSITION(src, 0x8C); +ASSERT_REG_POSITION(operation, 0xAB); +ASSERT_REG_POSITION(trigger, 0xB5); +#undef ASSERT_REG_POSITION + } // namespace Engines } // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 9f699399f..86e9dc998 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -2,23 +2,130 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "common/assert.h" +#include "core/core.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/video_core.h" namespace Tegra { namespace Engines { -Maxwell3D::Maxwell3D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +/// First register id that is actually a Macro call. +constexpr u32 MacroRegistersStart = 0xE00; + +Maxwell3D::Maxwell3D(MemoryManager& memory_manager) + : memory_manager(memory_manager), macro_interpreter(*this) {} + +void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { + auto macro_code = uploaded_macros.find(method); + // The requested macro must have been uploaded already. + ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method); + + // Reset the current macro and execute it. + executing_macro = 0; + macro_interpreter.Execute(macro_code->second, std::move(parameters)); +} + +void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + + // 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); + } + + // Methods after 0xE00 are special, they're actually triggers for some microcode that was + // uploaded to the GPU during initialization. + if (method >= MacroRegistersStart) { + // We're trying to execute a macro + if (executing_macro == 0) { + // A macro call must begin by writing the macro method's register, not its argument. + ASSERT_MSG((method % 2) == 0, + "Can't start macro execution by writing to the ARGS register"); + executing_macro = method; + } + + macro_params.push_back(value); + + // Call the macro when there are no more parameters in the command buffer + if (remaining_params == 0) { + CallMacroMethod(executing_macro, std::move(macro_params)); + } + return; + } -void Maxwell3D::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); - regs.reg_array[method] = value; + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); + } -#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) + regs.reg_array[method] = value; switch (method) { + case MAXWELL3D_REG_INDEX(macros.data): { + ProcessMacroUpload(value); + break; + } + case MAXWELL3D_REG_INDEX(code_address.code_address_high): + case MAXWELL3D_REG_INDEX(code_address.code_address_low): { + // Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS + // register, we do not currently know if that's intended or a bug, so we assert it lest + // stuff breaks in other places (like the shader address calculation). + ASSERT_MSG(regs.code_address.CodeAddress() == 0, "Unexpected CODE_ADDRESS register value."); + break; + } + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { + ProcessCBData(value); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): { + ProcessCBBind(Regs::ShaderStage::Vertex); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[1].raw_config): { + ProcessCBBind(Regs::ShaderStage::TesselationControl); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[2].raw_config): { + ProcessCBBind(Regs::ShaderStage::TesselationEval); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[3].raw_config): { + ProcessCBBind(Regs::ShaderStage::Geometry); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[4].raw_config): { + ProcessCBBind(Regs::ShaderStage::Fragment); + break; + } + case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): { + DrawArrays(); + break; + } case MAXWELL3D_REG_INDEX(query.query_get): { ProcessQueryGet(); break; @@ -27,25 +134,285 @@ void Maxwell3D::WriteReg(u32 method, u32 value) { break; } -#undef MAXWELL3D_REG_INDEX + VideoCore::g_renderer->Rasterizer()->NotifyMaxwellRegisterChanged(method); + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); + } +} + +void Maxwell3D::ProcessMacroUpload(u32 data) { + // Store the uploaded macro code to interpret them when they're called. + auto& macro = uploaded_macros[regs.macros.entry * 2 + MacroRegistersStart]; + macro.push_back(data); } void Maxwell3D::ProcessQueryGet() { GPUVAddr sequence_address = regs.query.QueryAddress(); // Since the sequence address is given as a GPU VAddr, we have to convert it to an application // VAddr before writing. - VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address); + boost::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address); + + // TODO(Subv): Support the other query units. + ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop, + "Units other than CROP are unimplemented"); + + u32 value = Memory::Read32(*address); + u64 result = 0; + + // TODO(Subv): Support the other query variables + switch (regs.query.query_get.select) { + case Regs::QuerySelect::Zero: + // This seems to actually write the query sequence to the query address. + result = regs.query.query_sequence; + break; + default: + UNIMPLEMENTED_MSG("Unimplemented query select type {}", + static_cast<u32>(regs.query.query_get.select.Value())); + } + + // TODO(Subv): Research and implement how query sync conditions work. + + struct LongQueryResult { + u64_le value; + u64_le timestamp; + }; + static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size"); switch (regs.query.query_get.mode) { - case Regs::QueryMode::Write: { - // Write the current query sequence to the sequence address. + case Regs::QueryMode::Write: + case Regs::QueryMode::Write2: { u32 sequence = regs.query.query_sequence; - Memory::Write32(address, sequence); + if (regs.query.query_get.short_query) { + // Write the current query sequence to the sequence address. + // TODO(Subv): Find out what happens if you use a long query type but mark it as a short + // query. + Memory::Write32(*address, sequence); + } else { + // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast + // GPU, this command may actually take a while to complete in real hardware due to GPU + // wait queues. + LongQueryResult query_result{}; + query_result.value = result; + // TODO(Subv): Generate a real GPU timestamp and write it here instead of 0 + query_result.timestamp = 0; + Memory::WriteBlock(*address, &query_result, sizeof(query_result)); + } break; } default: - UNIMPLEMENTED_MSG("Query mode %u not implemented", regs.query.query_get.mode.Value()); + UNIMPLEMENTED_MSG("Query mode {} not implemented", + static_cast<u32>(regs.query.query_get.mode.Value())); + } +} + +void Maxwell3D::DrawArrays() { + NGLOG_DEBUG(HW_GPU, "called, topology={}, count={}", + static_cast<u32>(regs.draw.topology.Value()), regs.vertex_buffer.count); + ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); + + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); + } + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } + + const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; + VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(is_indexed); + + // 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_array.count = 0; + } else { + regs.vertex_buffer.count = 0; + } +} + +void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { + // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage. + auto& shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)]; + + auto& buffer = shader.const_buffers[bind_data.index]; + + buffer.enabled = bind_data.valid.Value() != 0; + buffer.index = bind_data.index; + buffer.address = regs.const_buffer.BufferAddress(); + buffer.size = regs.const_buffer.cb_size; +} + +void Maxwell3D::ProcessCBData(u32 value) { + // Write the input value to the current const buffer at the current position. + GPUVAddr buffer_address = regs.const_buffer.BufferAddress(); + ASSERT(buffer_address != 0); + + // Don't allow writing past the end of the buffer. + ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size); + + boost::optional<VAddr> address = + memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos); + + Memory::Write32(*address, value); + + // Increment the current buffer position. + regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; +} + +Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry); + boost::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu); + + Texture::TICEntry tic_entry; + Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + + ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear || + tic_entry.header_version == Texture::TICHeaderVersion::Pitch, + "TIC versions other than BlockLinear or Pitch are unimplemented"); + + ASSERT_MSG((tic_entry.texture_type == Texture::TextureType::Texture2D) || + (tic_entry.texture_type == Texture::TextureType::Texture2DNoMipmap), + "Texture types other than Texture2D are unimplemented"); + + auto r_type = tic_entry.r_type.Value(); + auto g_type = tic_entry.g_type.Value(); + auto b_type = tic_entry.b_type.Value(); + auto a_type = tic_entry.a_type.Value(); + + // TODO(Subv): Different data types for separate components are not supported + ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + // TODO(Subv): Only UNORM formats are supported for now. + ASSERT(r_type == Texture::ComponentType::UNORM); + + return tic_entry; +} + +Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { + GPUVAddr tsc_base_address = regs.tsc.TSCAddress(); + + GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry); + boost::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu); + + Texture::TSCEntry tsc_entry; + Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; +} + +std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const { + std::vector<Texture::FullTextureInfo> textures; + + auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; + + // Offset into the texture constbuffer where the texture info begins. + static constexpr size_t TextureInfoOffset = 0x20; + + for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; + current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) { + + Texture::TextureHandle tex_handle{ + Memory::Read32(*memory_manager.GpuToCpuAddress(current_texture))}; + + Texture::FullTextureInfo tex_info{}; + // TODO(Subv): Use the shader to determine which textures are actually accessed. + tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) / + sizeof(Texture::TextureHandle); + + // Load the TIC data. + if (tex_handle.tic_id != 0) { + tex_info.enabled = true; + + auto tic_entry = GetTICEntry(tex_handle.tic_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry)); + } + + // Load the TSC data + if (tex_handle.tsc_id != 0) { + auto tsc_entry = GetTSCEntry(tex_handle.tsc_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry)); + } + + if (tex_info.enabled) + textures.push_back(tex_info); + } + + return textures; +} + +Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage, size_t offset) const { + auto& shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tex_info_address = tex_info_buffer.address + offset * sizeof(Texture::TextureHandle); + + ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size); + + boost::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address); + Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)}; + + Texture::FullTextureInfo tex_info{}; + tex_info.index = static_cast<u32>(offset); + + // Load the TIC data. + if (tex_handle.tic_id != 0) { + tex_info.enabled = true; + + auto tic_entry = GetTICEntry(tex_handle.tic_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry)); + } + + // Load the TSC data + if (tex_handle.tsc_id != 0) { + auto tsc_entry = GetTSCEntry(tex_handle.tsc_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry)); + } + + return tex_info; +} + +u32 Maxwell3D::GetRegisterValue(u32 method) const { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register"); + return regs.reg_array[method]; +} + +bool Maxwell3D::IsShaderStageEnabled(Regs::ShaderStage stage) const { + // The Vertex stage is always enabled. + if (stage == Regs::ShaderStage::Vertex) + return true; + + switch (stage) { + case Regs::ShaderStage::TesselationControl: + return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationControl)] + .enable != 0; + case Regs::ShaderStage::TesselationEval: + return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationEval)] + .enable != 0; + case Regs::ShaderStage::Geometry: + return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Geometry)].enable != 0; + case Regs::ShaderStage::Fragment: + return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Fragment)].enable != 0; } + + UNREACHABLE(); } + } // namespace Engines } // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 1eeef6857..2dc251205 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -4,35 +4,538 @@ #pragma once +#include <array> +#include <unordered_map> +#include <vector> +#include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/macro_interpreter.h" #include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" namespace Tegra { namespace Engines { +#define MAXWELL3D_REG_INDEX(field_name) \ + (offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32)) + class Maxwell3D final { public: explicit Maxwell3D(MemoryManager& memory_manager); ~Maxwell3D() = default; - /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value); - /// Register structure of the Maxwell3D engine. /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. struct Regs { - static constexpr size_t NUM_REGS = 0xE36; + static constexpr size_t NUM_REGS = 0xE00; + + static constexpr size_t NumRenderTargets = 8; + static constexpr size_t NumViewports = 16; + static constexpr size_t NumCBData = 16; + static constexpr size_t NumVertexArrays = 32; + static constexpr size_t NumVertexAttributes = 32; + static constexpr size_t MaxShaderProgram = 6; + static constexpr size_t MaxShaderStage = 5; + // Maximum number of const buffers per shader stage. + static constexpr size_t MaxConstBuffers = 16; enum class QueryMode : u32 { Write = 0, Sync = 1, + // TODO(Subv): It is currently unknown what the difference between method 2 and method 0 + // is. + Write2 = 2, + }; + + enum class QueryUnit : u32 { + VFetch = 1, + VP = 2, + Rast = 4, + StrmOut = 5, + GP = 6, + ZCull = 7, + Prop = 10, + Crop = 15, + }; + + enum class QuerySelect : u32 { + Zero = 0, + }; + + enum class QuerySyncCondition : u32 { + NotEqual = 0, + GreaterThan = 1, + }; + + enum class ShaderProgram : u32 { + VertexA = 0, + VertexB = 1, + TesselationControl = 2, + TesselationEval = 3, + Geometry = 4, + Fragment = 5, + }; + + enum class ShaderStage : u32 { + Vertex = 0, + TesselationControl = 1, + TesselationEval = 2, + Geometry = 3, + Fragment = 4, + }; + + struct VertexAttribute { + enum class Size : u32 { + Size_32_32_32_32 = 0x01, + Size_32_32_32 = 0x02, + Size_16_16_16_16 = 0x03, + Size_32_32 = 0x04, + Size_16_16_16 = 0x05, + Size_8_8_8_8 = 0x0a, + Size_16_16 = 0x0f, + Size_32 = 0x12, + Size_8_8_8 = 0x13, + Size_8_8 = 0x18, + Size_16 = 0x1b, + Size_8 = 0x1d, + Size_10_10_10_2 = 0x30, + Size_11_11_10 = 0x31, + }; + + enum class Type : u32 { + SignedNorm = 1, + UnsignedNorm = 2, + SignedInt = 3, + UnsignedInt = 4, + UnsignedScaled = 5, + SignedScaled = 6, + Float = 7, + }; + + union { + BitField<0, 5, u32> buffer; + BitField<6, 1, u32> constant; + BitField<7, 14, u32> offset; + BitField<21, 6, Size> size; + BitField<27, 3, Type> type; + BitField<31, 1, u32> bgra; + }; + + u32 ComponentCount() const { + switch (size) { + case Size::Size_32_32_32_32: + return 4; + case Size::Size_32_32_32: + return 3; + case Size::Size_16_16_16_16: + return 4; + case Size::Size_32_32: + return 2; + case Size::Size_16_16_16: + return 3; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 2; + case Size::Size_32: + return 1; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 1; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 3; + default: + UNREACHABLE(); + } + } + + u32 SizeInBytes() const { + switch (size) { + case Size::Size_32_32_32_32: + return 16; + case Size::Size_32_32_32: + return 12; + case Size::Size_16_16_16_16: + return 8; + case Size::Size_32_32: + return 8; + case Size::Size_16_16_16: + return 6; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 4; + case Size::Size_32: + return 4; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 2; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 4; + default: + UNREACHABLE(); + } + } + + std::string SizeString() const { + switch (size) { + case Size::Size_32_32_32_32: + return "32_32_32_32"; + case Size::Size_32_32_32: + return "32_32_32"; + case Size::Size_16_16_16_16: + return "16_16_16_16"; + case Size::Size_32_32: + return "32_32"; + case Size::Size_16_16_16: + return "16_16_16"; + case Size::Size_8_8_8_8: + return "8_8_8_8"; + case Size::Size_16_16: + return "16_16"; + case Size::Size_32: + return "32"; + case Size::Size_8_8_8: + return "8_8_8"; + case Size::Size_8_8: + return "8_8"; + case Size::Size_16: + return "16"; + case Size::Size_8: + return "8"; + case Size::Size_10_10_10_2: + return "10_10_10_2"; + case Size::Size_11_11_10: + return "11_11_10"; + } + UNREACHABLE(); + return {}; + } + + std::string TypeString() const { + switch (type) { + case Type::SignedNorm: + return "SNORM"; + case Type::UnsignedNorm: + return "UNORM"; + case Type::SignedInt: + return "SINT"; + case Type::UnsignedInt: + return "UINT"; + case Type::UnsignedScaled: + return "USCALED"; + case Type::SignedScaled: + return "SSCALED"; + case Type::Float: + return "FLOAT"; + } + UNREACHABLE(); + return {}; + } + + bool IsNormalized() const { + return (type == Type::SignedNorm) || (type == Type::UnsignedNorm); + } + }; + + enum class PrimitiveTopology : u32 { + Points = 0x0, + Lines = 0x1, + LineLoop = 0x2, + LineStrip = 0x3, + Triangles = 0x4, + TriangleStrip = 0x5, + TriangleFan = 0x6, + Quads = 0x7, + QuadStrip = 0x8, + Polygon = 0x9, + LinesAdjacency = 0xa, + LineStripAdjacency = 0xb, + TrianglesAdjacency = 0xc, + TriangleStripAdjacency = 0xd, + Patches = 0xe, + }; + + enum class IndexFormat : u32 { + UnsignedByte = 0x0, + UnsignedShort = 0x1, + UnsignedInt = 0x2, + }; + + struct Blend { + enum class Equation : u32 { + Add = 1, + Subtract = 2, + ReverseSubtract = 3, + Min = 4, + Max = 5, + }; + + enum class Factor : u32 { + Zero = 0x1, + One = 0x2, + SourceColor = 0x3, + OneMinusSourceColor = 0x4, + SourceAlpha = 0x5, + OneMinusSourceAlpha = 0x6, + DestAlpha = 0x7, + OneMinusDestAlpha = 0x8, + DestColor = 0x9, + OneMinusDestColor = 0xa, + SourceAlphaSaturate = 0xb, + Source1Color = 0x10, + OneMinusSource1Color = 0x11, + Source1Alpha = 0x12, + OneMinusSource1Alpha = 0x13, + ConstantColor = 0x61, + OneMinusConstantColor = 0x62, + ConstantAlpha = 0x63, + OneMinusConstantAlpha = 0x64, + }; + + u32 separate_alpha; + Equation equation_rgb; + Factor factor_source_rgb; + Factor factor_dest_rgb; + Equation equation_a; + Factor factor_source_a; + Factor factor_dest_a; + INSERT_PADDING_WORDS(1); }; union { struct { - INSERT_PADDING_WORDS(0x6C0); + INSERT_PADDING_WORDS(0x45); + + struct { + INSERT_PADDING_WORDS(1); + u32 data; + u32 entry; + } macros; + + INSERT_PADDING_WORDS(0x1B8); + + struct { + u32 address_high; + u32 address_low; + u32 width; + u32 height; + Tegra::RenderTargetFormat format; + u32 block_dimensions; + u32 array_mode; + u32 layer_stride; + u32 base_layer; + INSERT_PADDING_WORDS(7); + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } rt[NumRenderTargets]; + + struct { + f32 scale_x; + f32 scale_y; + f32 scale_z; + f32 translate_x; + f32 translate_y; + f32 translate_z; + INSERT_PADDING_WORDS(2); + + MathUtil::Rectangle<s32> GetRect() const { + return { + GetX(), // left + GetY() + GetHeight(), // top + GetX() + GetWidth(), // right + GetY() // bottom + }; + }; + + s32 GetX() const { + return static_cast<s32>(std::max(0.0f, translate_x - std::fabs(scale_x))); + } + + s32 GetY() const { + return static_cast<s32>(std::max(0.0f, translate_y - std::fabs(scale_y))); + } + + s32 GetWidth() const { + return static_cast<s32>(translate_x + std::fabs(scale_x)) - GetX(); + } + + s32 GetHeight() const { + return static_cast<s32>(translate_y + std::fabs(scale_y)) - GetY(); + } + } viewport_transform[NumViewports]; + + struct { + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> width; + }; + union { + BitField<0, 16, u32> y; + BitField<16, 16, u32> height; + }; + float depth_range_near; + float depth_range_far; + } viewport[NumViewports]; + + INSERT_PADDING_WORDS(0x1D); + + struct { + u32 first; + u32 count; + } vertex_buffer; + + INSERT_PADDING_WORDS(0x99); + + struct { + u32 address_high; + u32 address_low; + u32 format; + u32 block_dimensions; + u32 layer_stride; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } zeta; + + INSERT_PADDING_WORDS(0x5B); + + VertexAttribute vertex_attrib_format[NumVertexAttributes]; + + INSERT_PADDING_WORDS(0xF); + + struct { + union { + BitField<0, 4, u32> count; + }; + } rt_control; + + INSERT_PADDING_WORDS(0x31); + + u32 independent_blend_enable; + + INSERT_PADDING_WORDS(0x15); + + struct { + u32 separate_alpha; + Blend::Equation equation_rgb; + Blend::Factor factor_source_rgb; + Blend::Factor factor_dest_rgb; + Blend::Equation equation_a; + Blend::Factor factor_source_a; + INSERT_PADDING_WORDS(1); + Blend::Factor factor_dest_a; + + u32 enable_common; + u32 enable[NumRenderTargets]; + } blend; + + INSERT_PADDING_WORDS(0x77); + + struct { + u32 tsc_address_high; + u32 tsc_address_low; + u32 tsc_limit; + + GPUVAddr TSCAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low); + } + } tsc; + + INSERT_PADDING_WORDS(0x3); + + struct { + u32 tic_address_high; + u32 tic_address_low; + u32 tic_limit; + + GPUVAddr TICAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low); + } + } tic; + + INSERT_PADDING_WORDS(0x22); + + struct { + u32 code_address_high; + u32 code_address_low; + + GPUVAddr CodeAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low); + } + } code_address; + INSERT_PADDING_WORDS(1); + + struct { + u32 vertex_end_gl; + union { + u32 vertex_begin_gl; + BitField<0, 16, PrimitiveTopology> topology; + }; + } draw; + + INSERT_PADDING_WORDS(0x6B); + + struct { + u32 start_addr_high; + u32 start_addr_low; + u32 end_addr_high; + u32 end_addr_low; + IndexFormat format; + u32 first; + u32 count; + + unsigned FormatSizeInBytes() const { + switch (format) { + case IndexFormat::UnsignedByte: + return 1; + case IndexFormat::UnsignedShort: + return 2; + case IndexFormat::UnsignedInt: + return 4; + } + UNREACHABLE(); + } + + GPUVAddr StartAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(start_addr_high) << 32) | start_addr_low); + } + + GPUVAddr EndAddress() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) | + end_addr_low); + } + } index_array; + + INSERT_PADDING_WORDS(0xC7); + struct { u32 query_address_high; u32 query_address_low; @@ -41,7 +544,10 @@ public: u32 raw; BitField<0, 2, QueryMode> mode; BitField<4, 1, u32> fence; - BitField<12, 4, u32> unit; + BitField<12, 4, QueryUnit> unit; + BitField<16, 1, QuerySyncCondition> sync_cond; + BitField<23, 5, QuerySelect> select; + BitField<28, 1, u32> short_query; } query_get; GPUVAddr QueryAddress() const { @@ -49,7 +555,100 @@ public: (static_cast<GPUVAddr>(query_address_high) << 32) | query_address_low); } } query; - INSERT_PADDING_WORDS(0x772); + + INSERT_PADDING_WORDS(0x3C); + + struct { + union { + BitField<0, 12, u32> stride; + BitField<12, 1, u32> enable; + }; + u32 start_high; + u32 start_low; + u32 divisor; + + GPUVAddr StartAddress() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(start_high) << 32) | + start_low); + } + + bool IsEnabled() const { + return enable != 0 && StartAddress() != 0; + } + + } vertex_array[NumVertexArrays]; + + Blend independent_blend[NumRenderTargets]; + + struct { + u32 limit_high; + u32 limit_low; + + GPUVAddr LimitAddress() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) | + limit_low); + } + } vertex_array_limit[NumVertexArrays]; + + struct { + union { + BitField<0, 1, u32> enable; + BitField<4, 4, ShaderProgram> program; + }; + u32 offset; + INSERT_PADDING_WORDS(14); + } shader_config[MaxShaderProgram]; + + INSERT_PADDING_WORDS(0x80); + + struct { + u32 cb_size; + u32 cb_address_high; + u32 cb_address_low; + u32 cb_pos; + u32 cb_data[NumCBData]; + + GPUVAddr BufferAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(cb_address_high) << 32) | cb_address_low); + } + } const_buffer; + + INSERT_PADDING_WORDS(0x10); + + struct { + union { + u32 raw_config; + BitField<0, 1, u32> valid; + BitField<4, 5, u32> index; + }; + INSERT_PADDING_WORDS(7); + } cb_bind[MaxShaderStage]; + + INSERT_PADDING_WORDS(0x56); + + u32 tex_cb_index; + + INSERT_PADDING_WORDS(0x395); + + struct { + /// Compressed address of a buffer that holds information about bound SSBOs. + /// This address is usually bound to c0 in the shaders. + u32 buffer_address; + + GPUVAddr BufferAddress() const { + return static_cast<GPUVAddr>(buffer_address) << 8; + } + } ssbo_info; + + INSERT_PADDING_WORDS(0x11); + + struct { + u32 address[MaxShaderStage]; + u32 size[MaxShaderStage]; + } tex_info_buffers; + + INSERT_PADDING_WORDS(0xCC); }; std::array<u32, NUM_REGS> reg_array; }; @@ -57,18 +656,109 @@ public: static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size"); + struct State { + struct ConstBufferInfo { + GPUVAddr address; + u32 index; + u32 size; + bool enabled; + }; + + struct ShaderStageInfo { + std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers; + }; + + std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages; + }; + + State state{}; + MemoryManager& memory_manager; + + /// Reads a register value located at the input method address + u32 GetRegisterValue(u32 method) const; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value, u32 remaining_params); + + /// Returns a list of enabled textures for the specified shader stage. + std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const; + + /// Returns the texture information for a specific texture in a specific shader stage. + Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, size_t offset) const; + + /// Returns whether the specified shader stage is enabled or not. + bool IsShaderStageEnabled(Regs::ShaderStage stage) const; + private: + std::unordered_map<u32, std::vector<u32>> uploaded_macros; + + /// 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. + std::vector<u32> macro_params; + + /// Interpreter for the macro codes uploaded to the GPU. + MacroInterpreter macro_interpreter; + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; + + /** + * Call a macro on this engine. + * @param method Method to call + * @param parameters Arguments to the method call + */ + void CallMacroMethod(u32 method, std::vector<u32> parameters); + + /// Handles writes to the macro uploading registers. + void ProcessMacroUpload(u32 data); + /// Handles a write to the QUERY_GET register. void ProcessQueryGet(); - MemoryManager& memory_manager; + /// Handles a write to the CB_DATA[i] register. + void ProcessCBData(u32 value); + + /// Handles a write to the CB_BIND register. + void ProcessCBBind(Regs::ShaderStage stage); + + /// Handles a write to the VERTEX_END_GL register, triggering a draw. + void DrawArrays(); }; #define ASSERT_REG_POSITION(field_name, position) \ static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \ "Field " #field_name " has invalid position") +ASSERT_REG_POSITION(macros, 0x45); +ASSERT_REG_POSITION(rt, 0x200); +ASSERT_REG_POSITION(viewport_transform[0], 0x280); +ASSERT_REG_POSITION(viewport, 0x300); +ASSERT_REG_POSITION(vertex_buffer, 0x35D); +ASSERT_REG_POSITION(zeta, 0x3F8); +ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458); +ASSERT_REG_POSITION(rt_control, 0x487); +ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); +ASSERT_REG_POSITION(blend, 0x4CF); +ASSERT_REG_POSITION(tsc, 0x557); +ASSERT_REG_POSITION(tic, 0x55D); +ASSERT_REG_POSITION(code_address, 0x582); +ASSERT_REG_POSITION(draw, 0x585); +ASSERT_REG_POSITION(index_array, 0x5F2); ASSERT_REG_POSITION(query, 0x6C0); +ASSERT_REG_POSITION(vertex_array[0], 0x700); +ASSERT_REG_POSITION(independent_blend, 0x780); +ASSERT_REG_POSITION(vertex_array_limit[0], 0x7C0); +ASSERT_REG_POSITION(shader_config[0], 0x800); +ASSERT_REG_POSITION(const_buffer, 0x8E0); +ASSERT_REG_POSITION(cb_bind[0], 0x904); +ASSERT_REG_POSITION(tex_cb_index, 0x982); +ASSERT_REG_POSITION(ssbo_info, 0xD18); +ASSERT_REG_POSITION(tex_info_buffers.address[0], 0xD2A); +ASSERT_REG_POSITION(tex_info_buffers.size[0], 0xD2F); #undef ASSERT_REG_POSITION diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp new file mode 100644 index 000000000..442138988 --- /dev/null +++ b/src/video_core/engines/maxwell_dma.cpp @@ -0,0 +1,69 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/memory.h" +#include "video_core/engines/maxwell_dma.h" +#include "video_core/textures/decoders.h" + +namespace Tegra { +namespace Engines { + +MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager) : memory_manager(memory_manager) {} + +void MaxwellDMA::WriteReg(u32 method, u32 value) { + ASSERT_MSG(method < Regs::NUM_REGS, + "Invalid MaxwellDMA register, increase the size of the Regs structure"); + + regs.reg_array[method] = value; + +#define MAXWELLDMA_REG_INDEX(field_name) \ + (offsetof(Tegra::Engines::MaxwellDMA::Regs, field_name) / sizeof(u32)) + + switch (method) { + case MAXWELLDMA_REG_INDEX(exec): { + HandleCopy(); + break; + } + } + +#undef MAXWELLDMA_REG_INDEX +} + +void MaxwellDMA::HandleCopy() { + NGLOG_WARNING(HW_GPU, "Requested a DMA copy"); + + const GPUVAddr source = regs.src_address.Address(); + const GPUVAddr dest = regs.dst_address.Address(); + + const VAddr source_cpu = *memory_manager.GpuToCpuAddress(source); + const VAddr dest_cpu = *memory_manager.GpuToCpuAddress(dest); + + // TODO(Subv): Perform more research and implement all features of this engine. + ASSERT(regs.exec.enable_swizzle == 0); + ASSERT(regs.exec.enable_2d == 1); + ASSERT(regs.exec.query_mode == Regs::QueryMode::None); + ASSERT(regs.exec.query_intr == Regs::QueryIntr::None); + ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2); + ASSERT(regs.src_params.pos_x == 0); + ASSERT(regs.src_params.pos_y == 0); + ASSERT(regs.dst_params.pos_x == 0); + ASSERT(regs.dst_params.pos_y == 0); + ASSERT(regs.exec.is_dst_linear != regs.exec.is_src_linear); + + u8* src_buffer = Memory::GetPointer(source_cpu); + u8* dst_buffer = Memory::GetPointer(dest_cpu); + + if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { + // If the input is tiled and the output is linear, deswizzle the input and copy it over. + Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer, + dst_buffer, true, regs.src_params.BlockHeight()); + } else { + // If the input is linear and the output is tiled, swizzle the input and copy it over. + Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer, + src_buffer, false, regs.dst_params.BlockHeight()); + } +} + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h new file mode 100644 index 000000000..905749bde --- /dev/null +++ b/src/video_core/engines/maxwell_dma.h @@ -0,0 +1,155 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +namespace Engines { + +class MaxwellDMA final { +public: + explicit MaxwellDMA(MemoryManager& memory_manager); + ~MaxwellDMA() = default; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value); + + struct Regs { + static constexpr size_t NUM_REGS = 0x1D6; + + struct Parameters { + union { + BitField<0, 4, u32> block_depth; + BitField<4, 4, u32> block_height; + BitField<8, 4, u32> block_width; + }; + u32 size_x; + u32 size_y; + u32 size_z; + u32 pos_z; + union { + BitField<0, 16, u32> pos_x; + BitField<16, 16, u32> pos_y; + }; + + u32 BlockHeight() const { + return 1 << block_height; + } + }; + + static_assert(sizeof(Parameters) == 24, "Parameters has wrong size"); + + enum class CopyMode : u32 { + None = 0, + Unk1 = 1, + Unk2 = 2, + }; + + enum class QueryMode : u32 { + None = 0, + Short = 1, + Long = 2, + }; + + enum class QueryIntr : u32 { + None = 0, + Block = 1, + NonBlock = 2, + }; + + union { + struct { + INSERT_PADDING_WORDS(0xC0); + + struct { + union { + BitField<0, 2, CopyMode> copy_mode; + BitField<2, 1, u32> flush; + + BitField<3, 2, QueryMode> query_mode; + BitField<5, 2, QueryIntr> query_intr; + + BitField<7, 1, u32> is_src_linear; + BitField<8, 1, u32> is_dst_linear; + + BitField<9, 1, u32> enable_2d; + BitField<10, 1, u32> enable_swizzle; + }; + } exec; + + INSERT_PADDING_WORDS(0x3F); + + struct { + u32 address_high; + u32 address_low; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } src_address; + + struct { + u32 address_high; + u32 address_low; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } dst_address; + + u32 src_pitch; + u32 dst_pitch; + u32 x_count; + u32 y_count; + + INSERT_PADDING_WORDS(0xBB); + + Parameters dst_params; + + INSERT_PADDING_WORDS(1); + + Parameters src_params; + + INSERT_PADDING_WORDS(0x13); + }; + std::array<u32, NUM_REGS> reg_array; + }; + } regs{}; + + MemoryManager& memory_manager; + +private: + /// Performs the copy from the source buffer to the destination buffer as configured in the + /// registers. + void HandleCopy(); +}; + +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(exec, 0xC0); +ASSERT_REG_POSITION(src_address, 0x100); +ASSERT_REG_POSITION(dst_address, 0x102); +ASSERT_REG_POSITION(src_pitch, 0x104); +ASSERT_REG_POSITION(dst_pitch, 0x105); +ASSERT_REG_POSITION(x_count, 0x106); +ASSERT_REG_POSITION(y_count, 0x107); +ASSERT_REG_POSITION(dst_params, 0x1C3); +ASSERT_REG_POSITION(src_params, 0x1CA); + +#undef ASSERT_REG_POSITION + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h new file mode 100644 index 000000000..cefd57f4c --- /dev/null +++ b/src/video_core/engines/shader_bytecode.h @@ -0,0 +1,735 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <bitset> +#include <cstring> +#include <map> +#include <string> +#include <vector> + +#include <boost/optional.hpp> + +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Tegra { +namespace Shader { + +struct Register { + /// Number of registers + static constexpr size_t NumRegisters = 256; + + /// Register 255 is special cased to always be 0 + static constexpr size_t ZeroIndex = 255; + + enum class Size : u64 { + Byte = 0, + Short = 1, + Word = 2, + Long = 3, + }; + + constexpr Register() = default; + + constexpr Register(u64 value) : value(value) {} + + constexpr operator u64() const { + return value; + } + + template <typename T> + constexpr u64 operator-(const T& oth) const { + return value - oth; + } + + template <typename T> + constexpr u64 operator&(const T& oth) const { + return value & oth; + } + + constexpr u64 operator&(const Register& oth) const { + return value & oth.value; + } + + constexpr u64 operator~() const { + return ~value; + } + + u64 GetSwizzledIndex(u64 elem) const { + elem = (value + elem) & 3; + return (value & ~3) + elem; + } + +private: + u64 value{}; +}; + +union Attribute { + Attribute() = default; + + constexpr explicit Attribute(u64 value) : value(value) {} + + enum class Index : u64 { + Position = 7, + Attribute_0 = 8, + // This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex + // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval + // shader. + TessCoordInstanceIDVertexID = 47, + }; + + union { + BitField<22, 2, u64> element; + BitField<24, 6, Index> index; + BitField<47, 3, u64> size; + } fmt20; + + union { + BitField<30, 2, u64> element; + BitField<32, 6, Index> index; + } fmt28; + + BitField<39, 8, u64> reg; + u64 value{}; +}; + +union Sampler { + Sampler() = default; + + constexpr explicit Sampler(u64 value) : value(value) {} + + enum class Index : u64 { + Sampler_0 = 8, + }; + + BitField<36, 13, Index> index; + u64 value{}; +}; + +} // namespace Shader +} // namespace Tegra + +namespace std { + +// TODO(bunnei): The below is forbidden by the C++ standard, but works fine. See #330. +template <> +struct make_unsigned<Tegra::Shader::Attribute> { + using type = Tegra::Shader::Attribute; +}; + +template <> +struct make_unsigned<Tegra::Shader::Register> { + using type = Tegra::Shader::Register; +}; + +} // namespace std + +namespace Tegra { +namespace Shader { + +enum class Pred : u64 { + UnusedIndex = 0x7, + NeverExecute = 0xF, +}; + +enum class PredCondition : u64 { + LessThan = 1, + Equal = 2, + LessEqual = 3, + GreaterThan = 4, + NotEqual = 5, + GreaterEqual = 6, + // TODO(Subv): Other condition types +}; + +enum class PredOperation : u64 { + And = 0, + Or = 1, + Xor = 2, +}; + +enum class LogicOperation : u64 { + And = 0, + Or = 1, + Xor = 2, + PassB = 3, +}; + +enum class SubOp : u64 { + Cos = 0x0, + Sin = 0x1, + Ex2 = 0x2, + Lg2 = 0x3, + Rcp = 0x4, + Rsq = 0x5, + Min = 0x8, +}; + +enum class F2iRoundingOp : u64 { + None = 0, + Floor = 1, + Ceil = 2, + Trunc = 3, +}; + +enum class F2fRoundingOp : u64 { + None = 0, + Pass = 3, + Round = 8, + Floor = 9, + Ceil = 10, + Trunc = 11, +}; + +enum class UniformType : u64 { + UnsignedByte = 0, + SignedByte = 1, + UnsignedShort = 2, + SignedShort = 3, + Single = 4, + Double = 5, +}; + +union Instruction { + Instruction& operator=(const Instruction& instr) { + value = instr.value; + return *this; + } + + constexpr Instruction(u64 value) : value{value} {} + + BitField<0, 8, Register> gpr0; + BitField<8, 8, Register> gpr8; + union { + BitField<16, 4, Pred> full_pred; + BitField<16, 3, u64> pred_index; + } pred; + BitField<19, 1, u64> negate_pred; + BitField<20, 8, Register> gpr20; + BitField<20, 7, SubOp> sub_op; + BitField<28, 8, Register> gpr28; + BitField<39, 8, Register> gpr39; + BitField<48, 16, u64> opcode; + + union { + BitField<20, 19, u64> imm20_19; + BitField<20, 32, s64> imm20_32; + BitField<45, 1, u64> negate_b; + BitField<46, 1, u64> abs_a; + BitField<48, 1, u64> negate_a; + BitField<49, 1, u64> abs_b; + BitField<50, 1, u64> saturate_d; + BitField<56, 1, u64> negate_imm; + + union { + BitField<39, 3, u64> pred; + BitField<42, 1, u64> negate_pred; + } fmnmx; + + union { + BitField<39, 1, u64> invert_a; + BitField<40, 1, u64> invert_b; + BitField<41, 2, LogicOperation> operation; + BitField<44, 2, u64> unk44; + BitField<48, 3, Pred> pred48; + } lop; + + union { + BitField<53, 2, LogicOperation> operation; + BitField<55, 1, u64> invert_a; + BitField<56, 1, u64> invert_b; + } lop32i; + + float GetImm20_19() const { + float result{}; + u32 imm{static_cast<u32>(imm20_19)}; + imm <<= 12; + imm |= negate_imm ? 0x80000000 : 0; + std::memcpy(&result, &imm, sizeof(imm)); + return result; + } + + float GetImm20_32() const { + float result{}; + s32 imm{static_cast<s32>(imm20_32)}; + std::memcpy(&result, &imm, sizeof(imm)); + return result; + } + + s32 GetSignedImm20_20() const { + u32 immediate = static_cast<u32>(imm20_19 | (negate_imm << 19)); + // Sign extend the 20-bit value. + u32 mask = 1U << (20 - 1); + return static_cast<s32>((immediate ^ mask) - mask); + } + } alu; + + union { + BitField<48, 1, u64> is_signed; + } shift; + + union { + BitField<39, 5, u64> shift_amount; + BitField<48, 1, u64> negate_b; + BitField<49, 1, u64> negate_a; + } alu_integer; + + union { + BitField<54, 1, u64> saturate; + BitField<56, 1, u64> negate_a; + } iadd32i; + + union { + BitField<20, 8, u64> shift_position; + BitField<28, 8, u64> shift_length; + BitField<48, 1, u64> negate_b; + BitField<49, 1, u64> negate_a; + + u64 GetLeftShiftValue() const { + return 32 - (shift_position + shift_length); + } + } bfe; + + union { + BitField<48, 1, u64> negate_b; + BitField<49, 1, u64> negate_c; + } ffma; + + union { + BitField<48, 3, UniformType> type; + BitField<44, 2, u64> unknown; + } ld_c; + + union { + BitField<0, 3, u64> pred0; + BitField<3, 3, u64> pred3; + BitField<7, 1, u64> abs_a; + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred; + BitField<43, 1, u64> neg_a; + BitField<44, 1, u64> abs_b; + BitField<45, 2, PredOperation> op; + BitField<47, 1, u64> ftz; + BitField<48, 4, PredCondition> cond; + BitField<56, 1, u64> neg_b; + } fsetp; + + union { + BitField<0, 3, u64> pred0; + BitField<3, 3, u64> pred3; + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred; + BitField<45, 2, PredOperation> op; + BitField<48, 1, u64> is_signed; + BitField<49, 3, PredCondition> cond; + } isetp; + + union { + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred; + BitField<43, 1, u64> neg_a; + BitField<44, 1, u64> abs_b; + BitField<45, 2, PredOperation> op; + BitField<48, 4, PredCondition> cond; + BitField<52, 1, u64> bf; + BitField<53, 1, u64> neg_b; + BitField<54, 1, u64> abs_a; + BitField<55, 1, u64> ftz; + BitField<56, 1, u64> neg_imm; + } fset; + + union { + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred; + BitField<44, 1, u64> bf; + BitField<45, 2, PredOperation> op; + BitField<48, 1, u64> is_signed; + BitField<49, 3, PredCondition> cond; + } iset; + + union { + BitField<8, 2, Register::Size> dest_size; + BitField<10, 2, Register::Size> src_size; + BitField<12, 1, u64> is_output_signed; + BitField<13, 1, u64> is_input_signed; + BitField<41, 2, u64> selector; + BitField<45, 1, u64> negate_a; + BitField<49, 1, u64> abs_a; + + union { + BitField<39, 2, F2iRoundingOp> rounding; + } f2i; + + union { + BitField<39, 4, F2fRoundingOp> rounding; + } f2f; + } conversion; + + union { + BitField<31, 4, u64> component_mask; + + bool IsComponentEnabled(size_t component) const { + return ((1 << component) & component_mask) != 0; + } + } tex; + + union { + BitField<50, 3, u64> component_mask_selector; + BitField<28, 8, Register> gpr28; + + bool HasTwoDestinations() const { + return gpr28.Value() != Register::ZeroIndex; + } + + bool IsComponentEnabled(size_t component) const { + static constexpr std::array<size_t, 5> one_dest_mask{0x1, 0x2, 0x4, 0x8, 0x3}; + static constexpr std::array<size_t, 5> two_dest_mask{0x7, 0xb, 0xd, 0xe, 0xf}; + const auto& mask{HasTwoDestinations() ? two_dest_mask : one_dest_mask}; + + ASSERT(component_mask_selector < mask.size()); + + return ((1 << component) & mask[component_mask_selector]) != 0; + } + } texs; + + union { + BitField<20, 24, u64> target; + BitField<5, 1, u64> constant_buffer; + + s32 GetBranchTarget() const { + // Sign extend the branch target offset + u32 mask = 1U << (24 - 1); + u32 value = static_cast<u32>(target); + // The branch offset is relative to the next instruction and is stored in bytes, so + // divide it by the size of an instruction and add 1 to it. + return static_cast<s32>((value ^ mask) - mask) / sizeof(Instruction) + 1; + } + } bra; + + union { + BitField<20, 14, u64> offset; + BitField<34, 5, u64> index; + } cbuf34; + + union { + BitField<20, 16, s64> offset; + BitField<36, 5, u64> index; + } cbuf36; + + BitField<61, 1, u64> is_b_imm; + BitField<60, 1, u64> is_b_gpr; + BitField<59, 1, u64> is_c_gpr; + + Attribute attribute; + Sampler sampler; + + u64 value; +}; +static_assert(sizeof(Instruction) == 0x8, "Incorrect structure size"); +static_assert(std::is_standard_layout<Instruction>::value, + "Structure does not have standard layout"); + +class OpCode { +public: + enum class Id { + KIL, + SSY, + BFE_C, + BFE_R, + BFE_IMM, + BRA, + LD_A, + LD_C, + ST_A, + TEX, + TEXQ, // Texture Query + TEXS, // Texture Fetch with scalar/non-vec4 source/destinations + TLDS, // Texture Load with scalar/non-vec4 source/destinations + EXIT, + IPA, + FFMA_IMM, // Fused Multiply and Add + FFMA_CR, + FFMA_RC, + FFMA_RR, + FADD_C, + FADD_R, + FADD_IMM, + FMUL_C, + FMUL_R, + FMUL_IMM, + FMUL32_IMM, + IADD_C, + IADD_R, + IADD_IMM, + IADD32I, + ISCADD_C, // Scale and Add + ISCADD_R, + ISCADD_IMM, + MUFU, // Multi-Function Operator + RRO_C, // Range Reduction Operator + RRO_R, + RRO_IMM, + F2F_C, + F2F_R, + F2F_IMM, + F2I_C, + F2I_R, + F2I_IMM, + I2F_C, + I2F_R, + I2F_IMM, + I2I_C, + I2I_R, + I2I_IMM, + LOP_C, + LOP_R, + LOP_IMM, + LOP32I, + MOV_C, + MOV_R, + MOV_IMM, + MOV32_IMM, + SHL_C, + SHL_R, + SHL_IMM, + SHR_C, + SHR_R, + SHR_IMM, + FMNMX_C, + FMNMX_R, + FMNMX_IMM, + IMNMX_C, + IMNMX_R, + IMNMX_IMM, + FSETP_C, // Set Predicate + FSETP_R, + FSETP_IMM, + FSET_C, + FSET_R, + FSET_IMM, + ISETP_C, + ISETP_IMM, + ISETP_R, + ISET_R, + ISET_C, + ISET_IMM, + PSETP, + XMAD_IMM, + XMAD_CR, + XMAD_RC, + XMAD_RR, + }; + + enum class Type { + Trivial, + Arithmetic, + ArithmeticInteger, + ArithmeticIntegerImmediate, + Bfe, + Shift, + Ffma, + Flow, + Memory, + FloatSet, + FloatSetPredicate, + IntegerSet, + IntegerSetPredicate, + PredicateSetPredicate, + Conversion, + Unknown, + }; + + class Matcher { + public: + Matcher(const char* const name, u16 mask, u16 expected, OpCode::Id id, OpCode::Type type) + : name{name}, mask{mask}, expected{expected}, id{id}, type{type} {} + + const char* GetName() const { + return name; + } + + u16 GetMask() const { + return mask; + } + + Id GetId() const { + return id; + } + + Type GetType() const { + return type; + } + + /** + * Tests to see if the given instruction is the instruction this matcher represents. + * @param instruction The instruction to test + * @returns true if the given instruction matches. + */ + bool Matches(u16 instruction) const { + return (instruction & mask) == expected; + } + + private: + const char* name; + u16 mask; + u16 expected; + Id id; + Type type; + }; + + static boost::optional<const Matcher&> Decode(Instruction instr) { + static const auto table{GetDecodeTable()}; + + const auto matches_instruction = [instr](const auto& matcher) { + return matcher.Matches(static_cast<u16>(instr.opcode)); + }; + + auto iter = std::find_if(table.begin(), table.end(), matches_instruction); + return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none; + } + +private: + struct Detail { + private: + static constexpr size_t opcode_bitsize = 16; + + /** + * Generates the mask and the expected value after masking from a given bitstring. + * A '0' in a bitstring indicates that a zero must be present at that bit position. + * A '1' in a bitstring indicates that a one must be present at that bit position. + */ + static auto GetMaskAndExpect(const char* const bitstring) { + u16 mask = 0, expect = 0; + for (size_t i = 0; i < opcode_bitsize; i++) { + const size_t bit_position = opcode_bitsize - i - 1; + switch (bitstring[i]) { + case '0': + mask |= 1 << bit_position; + break; + case '1': + expect |= 1 << bit_position; + mask |= 1 << bit_position; + break; + default: + // Ignore + break; + } + } + return std::make_tuple(mask, expect); + } + + public: + /// Creates a matcher that can match and parse instructions based on bitstring. + static auto GetMatcher(const char* const bitstring, OpCode::Id op, OpCode::Type type, + const char* const name) { + const auto mask_expect = GetMaskAndExpect(bitstring); + return Matcher(name, std::get<0>(mask_expect), std::get<1>(mask_expect), op, type); + } + }; + + static std::vector<Matcher> GetDecodeTable() { + std::vector<Matcher> table = { +#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name) + INST("111000110011----", Id::KIL, Type::Flow, "KIL"), + INST("111000101001----", Id::SSY, Type::Flow, "SSY"), + INST("111000100100----", Id::BRA, Type::Flow, "BRA"), + 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("1100000000111---", 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("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), + INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), + INST("001100101-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), + INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"), + INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"), + INST("010110011-------", Id::FFMA_RR, Type::Ffma, "FFMA_RR"), + INST("0100110001011---", Id::FADD_C, Type::Arithmetic, "FADD_C"), + INST("0101110001011---", Id::FADD_R, Type::Arithmetic, "FADD_R"), + INST("0011100-01011---", Id::FADD_IMM, Type::Arithmetic, "FADD_IMM"), + INST("0100110001101---", Id::FMUL_C, Type::Arithmetic, "FMUL_C"), + INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"), + INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"), + INST("00011110--------", Id::FMUL32_IMM, Type::Arithmetic, "FMUL32_IMM"), + INST("0100110000010---", Id::IADD_C, Type::ArithmeticInteger, "IADD_C"), + INST("0101110000010---", Id::IADD_R, Type::ArithmeticInteger, "IADD_R"), + INST("0011100-00010---", Id::IADD_IMM, Type::ArithmeticInteger, "IADD_IMM"), + INST("0001110---------", Id::IADD32I, Type::ArithmeticIntegerImmediate, "IADD32I"), + INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"), + INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"), + INST("0011100-00011---", Id::ISCADD_IMM, Type::ArithmeticInteger, "ISCADD_IMM"), + INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), + INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), + INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), + INST("0011100-10010---", Id::RRO_IMM, Type::Arithmetic, "RRO_IMM"), + INST("0100110010101---", Id::F2F_C, Type::Conversion, "F2F_C"), + INST("0101110010101---", Id::F2F_R, Type::Conversion, "F2F_R"), + INST("0011100-10101---", Id::F2F_IMM, Type::Conversion, "F2F_IMM"), + INST("0100110010110---", Id::F2I_C, Type::Conversion, "F2I_C"), + INST("0101110010110---", Id::F2I_R, Type::Conversion, "F2I_R"), + INST("0011100-10110---", Id::F2I_IMM, Type::Conversion, "F2I_IMM"), + INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"), + INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"), + INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"), + INST("000000010000----", Id::MOV32_IMM, Type::Arithmetic, "MOV32_IMM"), + INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"), + INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"), + INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"), + INST("0100110000100---", Id::IMNMX_C, Type::Arithmetic, "FMNMX_IMM"), + INST("0101110000100---", Id::IMNMX_R, Type::Arithmetic, "FMNMX_IMM"), + INST("0011100-00100---", Id::IMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"), + INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"), + INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"), + INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"), + INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"), + INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), + INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"), + INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"), + INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"), + INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"), + INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"), + INST("0100110000101---", Id::SHR_C, Type::Shift, "SHR_C"), + INST("0101110000101---", Id::SHR_R, Type::Shift, "SHR_R"), + INST("0011100-00101---", Id::SHR_IMM, Type::Shift, "SHR_IMM"), + INST("0100110011100---", Id::I2I_C, Type::Conversion, "I2I_C"), + INST("0101110011100---", Id::I2I_R, Type::Conversion, "I2I_R"), + INST("01110001-1000---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"), + INST("0100110010111---", Id::I2F_C, Type::Conversion, "I2F_C"), + INST("0101110010111---", Id::I2F_R, Type::Conversion, "I2F_R"), + INST("0011100-10111---", Id::I2F_IMM, Type::Conversion, "I2F_IMM"), + INST("01011000--------", Id::FSET_R, Type::FloatSet, "FSET_R"), + INST("0100100---------", Id::FSET_C, Type::FloatSet, "FSET_C"), + INST("0011000---------", Id::FSET_IMM, Type::FloatSet, "FSET_IMM"), + INST("010010111011----", Id::FSETP_C, Type::FloatSetPredicate, "FSETP_C"), + INST("010110111011----", Id::FSETP_R, Type::FloatSetPredicate, "FSETP_R"), + INST("0011011-1011----", Id::FSETP_IMM, Type::FloatSetPredicate, "FSETP_IMM"), + INST("010010110110----", Id::ISETP_C, Type::IntegerSetPredicate, "ISETP_C"), + INST("010110110110----", Id::ISETP_R, Type::IntegerSetPredicate, "ISETP_R"), + INST("0011011-0110----", Id::ISETP_IMM, Type::IntegerSetPredicate, "ISETP_IMM"), + INST("010110110101----", Id::ISET_R, Type::IntegerSet, "ISET_R"), + INST("010010110101----", Id::ISET_C, Type::IntegerSet, "ISET_C"), + INST("0011011-0101----", Id::ISET_IMM, Type::IntegerSet, "ISET_IMM"), + INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"), + INST("0011011-00------", Id::XMAD_IMM, Type::Arithmetic, "XMAD_IMM"), + INST("0100111---------", Id::XMAD_CR, Type::Arithmetic, "XMAD_CR"), + INST("010100010-------", Id::XMAD_RC, Type::Arithmetic, "XMAD_RC"), + INST("0101101100------", Id::XMAD_RR, Type::Arithmetic, "XMAD_RR"), + }; +#undef INST + std::stable_sort(table.begin(), table.end(), [](const auto& a, const auto& b) { + // If a matcher has more bits in its mask it is more specific, so it + // should come first. + return std::bitset<16>(a.GetMask()).count() > std::bitset<16>(b.GetMask()).count(); + }); + + return table; + } +}; + +} // namespace Shader +} // namespace Tegra diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp new file mode 100644 index 000000000..e36483145 --- /dev/null +++ b/src/video_core/gpu.cpp @@ -0,0 +1,43 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/engines/maxwell_compute.h" +#include "video_core/engines/maxwell_dma.h" +#include "video_core/gpu.h" + +namespace Tegra { + +GPU::GPU() { + memory_manager = std::make_unique<MemoryManager>(); + maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager); + fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager); + maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); + maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); +} + +GPU::~GPU() = default; + +const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const { + return *maxwell_3d; +} + +u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { + ASSERT(format != RenderTargetFormat::NONE); + + switch (format) { + case RenderTargetFormat::RGBA32_FLOAT: + return 16; + case RenderTargetFormat::RGBA16_FLOAT: + return 8; + case RenderTargetFormat::RGBA8_UNORM: + case RenderTargetFormat::RGB10_A2_UNORM: + return 4; + default: + UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format)); + } +} + +} // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index ba7781756..7b4e9b842 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -6,14 +6,66 @@ #include <memory> #include <unordered_map> +#include <vector> #include "common/common_types.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/engines/maxwell_compute.h" +#include "core/hle/service/nvflinger/buffer_queue.h" #include "video_core/memory_manager.h" namespace Tegra { +enum class RenderTargetFormat : u32 { + NONE = 0x0, + RGBA32_FLOAT = 0xC0, + RGBA16_FLOAT = 0xCA, + RGB10_A2_UNORM = 0xD1, + RGBA8_UNORM = 0xD5, + RGBA8_SRGB = 0xD6, + R11G11B10_FLOAT = 0xE0, +}; + +/// Returns the number of bytes per pixel of each rendertarget format. +u32 RenderTargetBytesPerPixel(RenderTargetFormat format); + +class DebugContext; + +/** + * Struct describing framebuffer configuration + */ +struct FramebufferConfig { + enum class PixelFormat : u32 { + ABGR8 = 1, + }; + + /** + * Returns the number of bytes per pixel. + */ + static u32 BytesPerPixel(PixelFormat format) { + switch (format) { + case PixelFormat::ABGR8: + return 4; + } + + UNREACHABLE(); + } + + VAddr address; + u32 offset; + u32 width; + u32 height; + u32 stride; + PixelFormat pixel_format; + + using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags; + TransformFlags transform_flags; +}; + +namespace Engines { +class Fermi2D; +class Maxwell3D; +class MaxwellCompute; +class MaxwellDMA; +} // namespace Engines + enum class EngineID { FERMI_TWOD_A = 0x902D, // 2D Engine MAXWELL_B = 0xB197, // 3D Engine @@ -24,22 +76,24 @@ enum class EngineID { class GPU final { public: - GPU() { - memory_manager = std::make_unique<MemoryManager>(); - maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager); - fermi_2d = std::make_unique<Engines::Fermi2D>(); - maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); - } - ~GPU() = default; + GPU(); + ~GPU(); /// Processes a command list stored at the specified address in GPU memory. void ProcessCommandList(GPUVAddr address, u32 size); + /// Returns a reference to the Maxwell3D GPU engine. + const Engines::Maxwell3D& Get3DEngine() const; + std::unique_ptr<MemoryManager> memory_manager; + Engines::Maxwell3D& Maxwell3D() { + return *maxwell_3d; + } + private: /// Writes a single register in the engine bound to the specified subchannel - void WriteReg(u32 method, u32 subchannel, u32 value); + void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params); /// Mapping of command subchannels to their bound engine ids. std::unordered_map<u32, EngineID> bound_engines; @@ -50,6 +104,8 @@ private: std::unique_ptr<Engines::Fermi2D> fermi_2d; /// Compute engine std::unique_ptr<Engines::MaxwellCompute> maxwell_compute; + /// DMA engine + std::unique_ptr<Engines::MaxwellDMA> maxwell_dma; }; } // namespace Tegra diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp new file mode 100644 index 000000000..44ece01c1 --- /dev/null +++ b/src/video_core/macro_interpreter.cpp @@ -0,0 +1,257 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/macro_interpreter.h" + +namespace Tegra { + +MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} + +void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) { + Reset(); + registers[1] = parameters[0]; + this->parameters = std::move(parameters); + + // Execute the code until we hit an exit condition. + bool keep_executing = true; + while (keep_executing) { + keep_executing = Step(code, false); + } + + // Assert the the macro used all the input parameters + ASSERT(next_parameter_index == this->parameters.size()); +} + +void MacroInterpreter::Reset() { + registers = {}; + pc = 0; + delayed_pc = boost::none; + method_address.raw = 0; + parameters.clear(); + // The next parameter index starts at 1, because $r1 already has the value of the first + // parameter. + next_parameter_index = 1; +} + +bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) { + u32 base_address = pc; + + Opcode opcode = GetOpcode(code); + pc += 4; + + // Update the program counter if we were delayed + if (delayed_pc != boost::none) { + ASSERT(is_delay_slot); + pc = *delayed_pc; + delayed_pc = boost::none; + } + + switch (opcode.operation) { + case Operation::ALU: { + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), + GetRegister(opcode.src_b)); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::AddImmediate: { + ProcessResult(opcode.result_operation, opcode.dst, + GetRegister(opcode.src_a) + opcode.immediate); + break; + } + case Operation::ExtractInsert: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); + dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); + dst |= src << opcode.bf_dst_bit; + ProcessResult(opcode.result_operation, opcode.dst, dst); + break; + } + case Operation::ExtractShiftLeftImmediate: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::ExtractShiftLeftRegister: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Read: { + u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Branch: { + ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); + u32 value = GetRegister(opcode.src_a); + bool taken = EvaluateBranchCondition(opcode.branch_condition, value); + if (taken) { + // Ignore the delay slot if the branch has the annul bit. + if (opcode.branch_annul) { + pc = base_address + (opcode.immediate << 2); + return true; + } + + delayed_pc = base_address + (opcode.immediate << 2); + // Execute one more instruction due to the delay slot. + return Step(code, true); + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented macro operation {}", + static_cast<u32>(opcode.operation.Value())); + } + + if (opcode.is_exit) { + // Exit has a delay slot, execute the next instruction + // Note: Executing an exit during a branch delay slot will cause the instruction at the + // branch target to be executed before exiting. + Step(code, true); + return false; + } + + return true; +} + +MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const { + ASSERT((pc % sizeof(u32)) == 0); + ASSERT(pc < code.size() * sizeof(u32)); + return {code[pc / sizeof(u32)]}; +} + +u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const { + switch (operation) { + case ALUOperation::Add: + return src_a + src_b; + // TODO(Subv): Implement AddWithCarry + case ALUOperation::Subtract: + return src_a - src_b; + // TODO(Subv): Implement SubtractWithBorrow + case ALUOperation::Xor: + return src_a ^ src_b; + case ALUOperation::Or: + return src_a | src_b; + case ALUOperation::And: + return src_a & src_b; + case ALUOperation::AndNot: + return src_a & ~src_b; + case ALUOperation::Nand: + return ~(src_a & src_b); + + default: + UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation)); + } +} + +void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) { + switch (operation) { + case ResultOperation::IgnoreAndFetch: + // Fetch parameter and ignore result. + SetRegister(reg, FetchParameter()); + break; + case ResultOperation::Move: + // Move result. + SetRegister(reg, result); + break; + case ResultOperation::MoveAndSetMethod: + // Move result and use as Method Address. + SetRegister(reg, result); + SetMethodAddress(result); + break; + case ResultOperation::FetchAndSend: + // Fetch parameter and send result. + SetRegister(reg, FetchParameter()); + Send(result); + break; + case ResultOperation::MoveAndSend: + // Move and send result. + SetRegister(reg, result); + Send(result); + break; + case ResultOperation::FetchAndSetMethod: + // Fetch parameter and use result as Method Address. + SetRegister(reg, FetchParameter()); + SetMethodAddress(result); + break; + case ResultOperation::MoveAndSetMethodFetchAndSend: + // Move result and use as Method Address, then fetch and send parameter. + SetRegister(reg, result); + SetMethodAddress(result); + Send(FetchParameter()); + break; + case ResultOperation::MoveAndSetMethodSend: + // Move result and use as Method Address, then send bits 12:17 of result. + SetRegister(reg, result); + SetMethodAddress(result); + Send((result >> 12) & 0b111111); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation)); + } +} + +u32 MacroInterpreter::FetchParameter() { + ASSERT(next_parameter_index < parameters.size()); + return parameters[next_parameter_index++]; +} + +u32 MacroInterpreter::GetRegister(u32 register_id) const { + // Register 0 is supposed to always return 0. + if (register_id == 0) + return 0; + + ASSERT(register_id < registers.size()); + return registers[register_id]; +} + +void MacroInterpreter::SetRegister(u32 register_id, u32 value) { + // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero + // register. + if (register_id == 0) + return; + + ASSERT(register_id < registers.size()); + registers[register_id] = value; +} + +void MacroInterpreter::SetMethodAddress(u32 address) { + method_address.raw = address; +} + +void MacroInterpreter::Send(u32 value) { + maxwell3d.WriteReg(method_address.address, value, 0); + // Increment the method address by the method increment. + method_address.address.Assign(method_address.address.Value() + + method_address.increment.Value()); +} + +u32 MacroInterpreter::Read(u32 method) const { + return maxwell3d.GetRegisterValue(method); +} + +bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const { + switch (cond) { + case BranchCondition::Zero: + return value == 0; + case BranchCondition::NotZero: + return value != 0; + } + UNREACHABLE(); +} + +} // namespace Tegra diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h new file mode 100644 index 000000000..a71e359d8 --- /dev/null +++ b/src/video_core/macro_interpreter.h @@ -0,0 +1,164 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include <boost/optional.hpp> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Tegra { +namespace Engines { +class Maxwell3D; +} + +class MacroInterpreter final { +public: + explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d); + + /** + * Executes the macro code with the specified input parameters. + * @param code The macro byte code to execute + * @param parameters The parameters of the macro + */ + void Execute(const std::vector<u32>& code, std::vector<u32> parameters); + +private: + enum class Operation : u32 { + ALU = 0, + AddImmediate = 1, + ExtractInsert = 2, + ExtractShiftLeftImmediate = 3, + ExtractShiftLeftRegister = 4, + Read = 5, + Unused = 6, // This operation doesn't seem to be a valid encoding. + Branch = 7, + }; + + enum class ALUOperation : u32 { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + // Operations 4-7 don't seem to be valid encodings. + Xor = 8, + Or = 9, + And = 10, + AndNot = 11, + Nand = 12 + }; + + enum class ResultOperation : u32 { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMethod = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMethod = 5, + MoveAndSetMethodFetchAndSend = 6, + MoveAndSetMethodSend = 7 + }; + + enum class BranchCondition : u32 { + Zero = 0, + NotZero = 1, + }; + + union Opcode { + u32 raw; + BitField<0, 3, Operation> operation; + BitField<4, 3, ResultOperation> result_operation; + BitField<4, 1, BranchCondition> branch_condition; + BitField<5, 1, u32> + branch_annul; // If set on a branch, then the branch doesn't have a delay slot. + BitField<7, 1, u32> is_exit; + BitField<8, 3, u32> dst; + BitField<11, 3, u32> src_a; + BitField<14, 3, u32> src_b; + // The signed immediate overlaps the second source operand and the alu operation. + BitField<14, 18, s32> immediate; + + BitField<17, 5, ALUOperation> alu_operation; + + // Bitfield instructions data + BitField<17, 5, u32> bf_src_bit; + BitField<22, 5, u32> bf_size; + BitField<27, 5, u32> bf_dst_bit; + + u32 GetBitfieldMask() const { + return (1 << bf_size) - 1; + } + }; + + union MethodAddress { + u32 raw; + BitField<0, 12, u32> address; + BitField<12, 6, u32> increment; + }; + + /// Resets the execution engine state, zeroing registers, etc. + void Reset(); + + /** + * Executes a single macro instruction located at the current program counter. Returns whether + * the interpreter should keep running. + * @param code The macro code to execute. + * @param is_delay_slot Whether the current step is being executed due to a delay slot in a + * previous instruction. + */ + bool Step(const std::vector<u32>& code, bool is_delay_slot); + + /// Calculates the result of an ALU operation. src_a OP src_b; + u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const; + + /// Performs the result operation on the input result and stores it in the specified register + /// (if necessary). + void ProcessResult(ResultOperation operation, u32 reg, u32 result); + + /// Evaluates the branch condition and returns whether the branch should be taken or not. + bool EvaluateBranchCondition(BranchCondition cond, u32 value) const; + + /// Reads an opcode at the current program counter location. + Opcode GetOpcode(const std::vector<u32>& code) const; + + /// Returns the specified register's value. Register 0 is hardcoded to always return 0. + u32 GetRegister(u32 register_id) const; + + /// Sets the register to the input value. + void SetRegister(u32 register_id, u32 value); + + /// Sets the method address to use for the next Send instruction. + void SetMethodAddress(u32 address); + + /// Calls a GPU Engine method with the input parameter. + void Send(u32 value); + + /// Reads a GPU register located at the method address. + u32 Read(u32 method) const; + + /// Returns the next parameter in the parameter queue. + u32 FetchParameter(); + + Engines::Maxwell3D& maxwell3d; + + u32 pc; ///< Current program counter + boost::optional<u32> + delayed_pc; ///< Program counter to execute at after the delay slot is executed. + + static constexpr size_t NumMacroRegisters = 8; + + /// General purpose macro registers. + std::array<u32, NumMacroRegisters> registers = {}; + + /// Method address to use for the next Send instruction. + MethodAddress method_address = {}; + + /// Input parameters of the current macro. + std::vector<u32> parameters; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; +}; +} // namespace Tegra diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 2789a4ca1..5cefce9fc 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -2,109 +2,137 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/alignment.h" #include "common/assert.h" #include "video_core/memory_manager.h" namespace Tegra { -PAddr MemoryManager::AllocateSpace(u64 size, u64 align) { - boost::optional<PAddr> paddr = FindFreeBlock(size, align); - ASSERT(paddr); +GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) { + boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align); + ASSERT(gpu_addr); - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - PageSlot(*paddr + offset) = static_cast<u64>(PageStatus::Allocated); + for (u64 offset = 0; offset < size; offset += PAGE_SIZE) { + ASSERT(PageSlot(*gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped)); + PageSlot(*gpu_addr + offset) = static_cast<u64>(PageStatus::Allocated); } - return *paddr; + return *gpu_addr; } -PAddr MemoryManager::AllocateSpace(PAddr paddr, u64 size, u64 align) { - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - if (IsPageMapped(paddr + offset)) { - return AllocateSpace(size, align); - } - } - - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - PageSlot(paddr + offset) = static_cast<u64>(PageStatus::Allocated); +GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) { + for (u64 offset = 0; offset < size; offset += PAGE_SIZE) { + ASSERT(PageSlot(gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped)); + PageSlot(gpu_addr + offset) = static_cast<u64>(PageStatus::Allocated); } - return paddr; + return gpu_addr; } -PAddr MemoryManager::MapBufferEx(VAddr vaddr, u64 size) { - vaddr &= ~Memory::PAGE_MASK; +GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) { + boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE); + ASSERT(gpu_addr); - boost::optional<PAddr> paddr = FindFreeBlock(size); - ASSERT(paddr); - - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - PageSlot(*paddr + offset) = vaddr + offset; + for (u64 offset = 0; offset < size; offset += PAGE_SIZE) { + ASSERT(PageSlot(*gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped)); + PageSlot(*gpu_addr + offset) = cpu_addr + offset; } - return *paddr; + MappedRegion region{cpu_addr, *gpu_addr, size}; + mapped_regions.push_back(region); + + return *gpu_addr; } -PAddr MemoryManager::MapBufferEx(VAddr vaddr, PAddr paddr, u64 size) { - vaddr &= ~Memory::PAGE_MASK; - paddr &= ~Memory::PAGE_MASK; +GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) { + ASSERT((gpu_addr & PAGE_MASK) == 0); - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - if (PageSlot(paddr + offset) != static_cast<u64>(PageStatus::Allocated)) { - return MapBufferEx(vaddr, size); - } + for (u64 offset = 0; offset < size; offset += PAGE_SIZE) { + ASSERT(PageSlot(gpu_addr + offset) == static_cast<u64>(PageStatus::Allocated)); + PageSlot(gpu_addr + offset) = cpu_addr + offset; } - for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { - PageSlot(paddr + offset) = vaddr + offset; + MappedRegion region{cpu_addr, gpu_addr, size}; + mapped_regions.push_back(region); + + return gpu_addr; +} + +GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { + ASSERT((gpu_addr & PAGE_MASK) == 0); + + for (u64 offset = 0; offset < size; offset += PAGE_SIZE) { + ASSERT(PageSlot(gpu_addr + offset) != static_cast<u64>(PageStatus::Allocated) && + PageSlot(gpu_addr + offset) != static_cast<u64>(PageStatus::Unmapped)); + PageSlot(gpu_addr + offset) = static_cast<u64>(PageStatus::Unmapped); } - return paddr; + // Delete the region mappings that are contained within the unmapped region + mapped_regions.erase(std::remove_if(mapped_regions.begin(), mapped_regions.end(), + [&](const MappedRegion& region) { + return region.gpu_addr <= gpu_addr && + region.gpu_addr + region.size < gpu_addr + size; + }), + mapped_regions.end()); + return gpu_addr; } -boost::optional<PAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) { - PAddr paddr{}; - u64 free_space{}; - align = (align + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; +boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) { + GPUVAddr gpu_addr = 0; + u64 free_space = 0; + align = (align + PAGE_MASK) & ~PAGE_MASK; - while (paddr + free_space < MAX_ADDRESS) { - if (!IsPageMapped(paddr + free_space)) { - free_space += Memory::PAGE_SIZE; + while (gpu_addr + free_space < MAX_ADDRESS) { + if (!IsPageMapped(gpu_addr + free_space)) { + free_space += PAGE_SIZE; if (free_space >= size) { - return paddr; + return gpu_addr; } } else { - paddr += free_space + Memory::PAGE_SIZE; + gpu_addr += free_space + PAGE_SIZE; free_space = 0; - const u64 remainder{paddr % align}; - if (!remainder) { - paddr = (paddr - remainder) + align; - } + gpu_addr = Common::AlignUp(gpu_addr, align); } } return {}; } -VAddr MemoryManager::PhysicalToVirtualAddress(PAddr paddr) { - VAddr base_addr = PageSlot(paddr); +boost::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) { + VAddr base_addr = PageSlot(gpu_addr); ASSERT(base_addr != static_cast<u64>(PageStatus::Unmapped)); - return base_addr + (paddr & Memory::PAGE_MASK); + + if (base_addr == static_cast<u64>(PageStatus::Allocated)) { + return {}; + } + + return base_addr + (gpu_addr & PAGE_MASK); +} + +std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const { + std::vector<GPUVAddr> results; + for (const auto& region : mapped_regions) { + if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) { + u64 offset = cpu_addr - region.cpu_addr; + results.push_back(region.gpu_addr + offset); + } + } + return results; } -bool MemoryManager::IsPageMapped(PAddr paddr) { - return PageSlot(paddr) != static_cast<u64>(PageStatus::Unmapped); +bool MemoryManager::IsPageMapped(GPUVAddr gpu_addr) { + return PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Unmapped); } -VAddr& MemoryManager::PageSlot(PAddr paddr) { - auto& block = page_table[(paddr >> (Memory::PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]; +VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) { + auto& block = page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]; if (!block) { block = std::make_unique<PageBlock>(); for (unsigned index = 0; index < PAGE_BLOCK_SIZE; index++) { (*block)[index] = static_cast<u64>(PageStatus::Unmapped); } } - return (*block)[(paddr >> Memory::PAGE_BITS) & PAGE_BLOCK_MASK]; + return (*block)[(gpu_addr >> PAGE_BITS) & PAGE_BLOCK_MASK]; } } // namespace Tegra diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 47da7acd6..86765e72a 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -6,8 +6,11 @@ #include <array> #include <memory> +#include <vector> + +#include <boost/optional.hpp> + #include "common/common_types.h" -#include "core/memory.h" namespace Tegra { @@ -18,16 +21,22 @@ class MemoryManager final { public: MemoryManager() = default; - PAddr AllocateSpace(u64 size, u64 align); - PAddr AllocateSpace(PAddr paddr, u64 size, u64 align); - PAddr MapBufferEx(VAddr vaddr, u64 size); - PAddr MapBufferEx(VAddr vaddr, PAddr paddr, u64 size); - VAddr PhysicalToVirtualAddress(PAddr paddr); + GPUVAddr AllocateSpace(u64 size, u64 align); + GPUVAddr AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align); + GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size); + GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size); + GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size); + boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr); + std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const; + + static constexpr u64 PAGE_BITS = 16; + static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS; + static constexpr u64 PAGE_MASK = PAGE_SIZE - 1; private: - boost::optional<PAddr> FindFreeBlock(u64 size, u64 align = 1); - bool IsPageMapped(PAddr paddr); - VAddr& PageSlot(PAddr paddr); + boost::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1); + bool IsPageMapped(GPUVAddr gpu_addr); + VAddr& PageSlot(GPUVAddr gpu_addr); enum class PageStatus : u64 { Unmapped = 0xFFFFFFFFFFFFFFFFULL, @@ -35,7 +44,7 @@ private: }; static constexpr u64 MAX_ADDRESS{0x10000000000ULL}; - static constexpr u64 PAGE_TABLE_BITS{14}; + static constexpr u64 PAGE_TABLE_BITS{10}; static constexpr u64 PAGE_TABLE_SIZE{1 << PAGE_TABLE_BITS}; static constexpr u64 PAGE_TABLE_MASK{PAGE_TABLE_SIZE - 1}; static constexpr u64 PAGE_BLOCK_BITS{14}; @@ -44,6 +53,14 @@ private: using PageBlock = std::array<VAddr, PAGE_BLOCK_SIZE>; std::array<std::unique_ptr<PageBlock>, PAGE_TABLE_SIZE> page_table{}; + + struct MappedRegion { + VAddr cpu_addr; + GPUVAddr gpu_addr; + u64 size; + }; + + std::vector<MappedRegion> mapped_regions; }; } // namespace Tegra diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h new file mode 100644 index 000000000..f0e48a802 --- /dev/null +++ b/src/video_core/rasterizer_interface.h @@ -0,0 +1,64 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +struct ScreenInfo; + +namespace VideoCore { + +class RasterizerInterface { +public: + virtual ~RasterizerInterface() {} + + /// Draw the current batch of vertex arrays + virtual void DrawArrays() = 0; + + /// Notify rasterizer that the specified Maxwell register has been changed + virtual void NotifyMaxwellRegisterChanged(u32 method) = 0; + + /// Notify rasterizer that all caches should be flushed to Switch memory + virtual void FlushAll() = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory + virtual void FlushRegion(Tegra::GPUVAddr addr, u64 size) = 0; + + /// Notify rasterizer that any caches of the specified region should be invalidated + virtual void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory + /// and invalidated + virtual void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0; + + /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0 + virtual bool AccelerateDisplayTransfer(const void* config) { + return false; + } + + /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1 + virtual bool AccelerateTextureCopy(const void* config) { + return false; + } + + /// Attempt to use a faster method to fill a region + virtual bool AccelerateFill(const void* config) { + return false; + } + + /// Attempt to use a faster method to display the framebuffer to screen + virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, + ScreenInfo& screen_info) { + return false; + } + + virtual bool AccelerateDrawBatch(bool is_indexed) { + return false; + } +}; +} // namespace VideoCore diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 51e1d45f9..30075b23c 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -5,6 +5,11 @@ #include <atomic> #include <memory> #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/video_core.h" -void RendererBase::RefreshRasterizerSetting() {} +void RendererBase::RefreshRasterizerSetting() { + if (rasterizer == nullptr) { + rasterizer = std::make_unique<RasterizerOpenGL>(); + } +} diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 2aba50eda..89a960eaf 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -8,6 +8,8 @@ #include <boost/optional.hpp> #include "common/assert.h" #include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" class EmuWindow; @@ -16,40 +18,10 @@ public: /// Used to reference a framebuffer enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; - /** - * Struct describing framebuffer metadata - * TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet. - */ - struct FramebufferInfo { - enum class PixelFormat : u32 { - ABGR8 = 1, - }; - - /** - * Returns the number of bytes per pixel. - */ - static u32 BytesPerPixel(PixelFormat format) { - switch (format) { - case PixelFormat::ABGR8: - return 4; - } - - UNREACHABLE(); - } - - VAddr address; - u32 offset; - u32 width; - u32 height; - u32 stride; - PixelFormat pixel_format; - bool flip_vertical; - }; - virtual ~RendererBase() {} /// Swap buffers (render frame) - virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0; + virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0; /** * Set the emulator window to use for renderer @@ -74,12 +46,16 @@ public: return m_current_frame; } + VideoCore::RasterizerInterface* Rasterizer() const { + return rasterizer.get(); + } + void RefreshRasterizerSetting(); protected: + std::unique_ptr<VideoCore::RasterizerInterface> rasterizer; f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer int m_current_frame = 0; ///< Current frame, should be set by the renderer private: - bool opengl_rasterizer_active = false; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp new file mode 100644 index 000000000..833ff8273 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -0,0 +1,758 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <glad/glad.h> +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/process.h" +#include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/maxwell_to_gl.h" +#include "video_core/renderer_opengl/renderer_opengl.h" + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; +using PixelFormat = SurfaceParams::PixelFormat; +using SurfaceType = SurfaceParams::SurfaceType; + +MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); +MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); + +RasterizerOpenGL::RasterizerOpenGL() { + has_ARB_buffer_storage = false; + has_ARB_direct_state_access = false; + has_ARB_separate_shader_objects = false; + has_ARB_vertex_attrib_binding = false; + + // Create sampler objects + for (size_t i = 0; i < texture_samplers.size(); ++i) { + texture_samplers[i].Create(); + state.texture_units[i].sampler = texture_samplers[i].sampler.handle; + } + + // Create SSBOs + for (size_t stage = 0; stage < ssbos.size(); ++stage) { + for (size_t buffer = 0; buffer < ssbos[stage].size(); ++buffer) { + ssbos[stage][buffer].Create(); + state.draw.const_buffers[stage][buffer].ssbo = ssbos[stage][buffer].handle; + } + } + + GLint ext_num; + glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num); + for (GLint i = 0; i < ext_num; i++) { + std::string extension{reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))}; + + if (extension == "GL_ARB_buffer_storage") { + has_ARB_buffer_storage = true; + } else if (extension == "GL_ARB_direct_state_access") { + has_ARB_direct_state_access = true; + } else if (extension == "GL_ARB_separate_shader_objects") { + has_ARB_separate_shader_objects = true; + } else if (extension == "GL_ARB_vertex_attrib_binding") { + has_ARB_vertex_attrib_binding = true; + } + } + + ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported"); + + // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0 + state.clip_distance[0] = true; + + // Generate VAO and UBO + sw_vao.Create(); + uniform_buffer.Create(); + + state.draw.vertex_array = sw_vao.handle; + state.draw.uniform_buffer = uniform_buffer.handle; + state.Apply(); + + // Create render framebuffer + framebuffer.Create(); + + hw_vao.Create(); + + stream_buffer = OGLStreamBuffer::MakeBuffer(has_ARB_buffer_storage, GL_ARRAY_BUFFER); + stream_buffer->Create(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE / 2); + state.draw.vertex_buffer = stream_buffer->GetHandle(); + + shader_program_manager = std::make_unique<GLShader::ProgramManager>(); + state.draw.shader_program = 0; + state.draw.vertex_array = hw_vao.handle; + state.Apply(); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer->GetHandle()); + + for (unsigned index = 0; index < uniform_buffers.size(); ++index) { + auto& buffer = uniform_buffers[index]; + buffer.Create(); + glBindBuffer(GL_UNIFORM_BUFFER, buffer.handle); + glBufferData(GL_UNIFORM_BUFFER, sizeof(GLShader::MaxwellUniformData), nullptr, + GL_STREAM_COPY); + glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer.handle); + } + + accelerate_draw = AccelDraw::Disabled; + + glEnable(GL_BLEND); + + NGLOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!"); +} + +RasterizerOpenGL::~RasterizerOpenGL() { + if (stream_buffer != nullptr) { + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + stream_buffer->Release(); + } +} + +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& memory_manager = Core::System().GetInstance().GPU().memory_manager; + + state.draw.vertex_array = hw_vao.handle; + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + // Upload all guest vertex arrays sequentially to our buffer + for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { + const auto& vertex_array = regs.vertex_array[index]; + if (!vertex_array.IsEnabled()) + continue; + + const Tegra::GPUVAddr start = vertex_array.StartAddress(); + const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + + ASSERT(end > start); + u64 size = end - start + 1; + + // Copy vertex array data + res_cache.FlushRegion(start, size, nullptr); + Memory::ReadBlock(*memory_manager->GpuToCpuAddress(start), array_ptr, size); + + // Bind the vertex array to the buffer at the current offset. + glBindVertexBuffer(index, stream_buffer->GetHandle(), buffer_offset, vertex_array.stride); + + ASSERT_MSG(vertex_array.divisor == 0, "Vertex buffer divisor unimplemented"); + + array_ptr += size; + buffer_offset += size; + } + + // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. + // Enables the first 16 vertex attributes always, as we don't know which ones are actually used + // until shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now + // to avoid OpenGL errors. + // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't + // assume every shader uses them all. + for (unsigned index = 0; index < 16; ++index) { + auto& attrib = regs.vertex_attrib_format[index]; + NGLOG_DEBUG(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}", + index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(), + attrib.offset.Value(), attrib.IsNormalized()); + + auto& buffer = regs.vertex_array[attrib.buffer]; + ASSERT(buffer.IsEnabled()); + + glEnableVertexAttribArray(index); + glVertexAttribFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); + glVertexAttribBinding(index, attrib.buffer); + } + + return {array_ptr, buffer_offset}; +} + +void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { + // Helper function for uploading uniform data + const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) { + if (has_ARB_direct_state_access) { + glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size); + } else { + glBindBuffer(GL_COPY_WRITE_BUFFER, handle); + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size); + } + }; + + auto& gpu = Core::System().GetInstance().GPU().Maxwell3D(); + ASSERT_MSG(!gpu.regs.shader_config[0].enable, "VertexA is unsupported!"); + + // Next available bindpoints to use when uploading the const buffers and textures to the GLSL + // shaders. + u32 current_constbuffer_bindpoint = 0; + u32 current_texture_bindpoint = 0; + + for (unsigned index = 1; index < Maxwell::MaxShaderProgram; ++index) { + auto& shader_config = gpu.regs.shader_config[index]; + const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; + + const auto& stage = index - 1; // Stage indices are 0 - 5 + + const bool is_enabled = gpu.IsShaderStageEnabled(static_cast<Maxwell::ShaderStage>(stage)); + + // Skip stages that are not enabled + if (!is_enabled) { + continue; + } + + GLShader::MaxwellUniformData ubo{}; + ubo.SetFromRegs(gpu.state.shader_stages[stage]); + std::memcpy(buffer_ptr, &ubo, sizeof(ubo)); + + // Flush the buffer so that the GPU can see the data we just wrote. + glFlushMappedBufferRange(GL_ARRAY_BUFFER, buffer_offset, sizeof(ubo)); + + // Upload uniform data as one UBO per stage + const GLintptr ubo_offset = buffer_offset; + copy_buffer(uniform_buffers[stage].handle, ubo_offset, + sizeof(GLShader::MaxwellUniformData)); + + buffer_ptr += sizeof(GLShader::MaxwellUniformData); + buffer_offset += sizeof(GLShader::MaxwellUniformData); + + // Fetch program code from memory + GLShader::ProgramCode program_code; + const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset}; + const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)}; + Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64)); + GLShader::ShaderSetup setup{std::move(program_code)}; + + GLShader::ShaderEntries shader_resources; + + switch (program) { + case Maxwell::ShaderProgram::VertexB: { + GLShader::MaxwellVSConfig vs_config{setup}; + shader_resources = + shader_program_manager->UseProgrammableVertexShader(vs_config, setup); + break; + } + case Maxwell::ShaderProgram::Fragment: { + GLShader::MaxwellFSConfig fs_config{setup}; + shader_resources = + shader_program_manager->UseProgrammableFragmentShader(fs_config, setup); + break; + } + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented shader index={}, enable={}, offset=0x{:08X}", + index, shader_config.enable.Value(), shader_config.offset); + UNREACHABLE(); + } + + GLuint gl_stage_program = shader_program_manager->GetCurrentProgramStage( + static_cast<Maxwell::ShaderStage>(stage)); + + // Configure the const buffers for this shader stage. + current_constbuffer_bindpoint = + SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program, + current_constbuffer_bindpoint, shader_resources.const_buffer_entries); + + // Configure the textures for this shader stage. + current_texture_bindpoint = + SetupTextures(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program, + current_texture_bindpoint, shader_resources.texture_samplers); + } + + shader_program_manager->UseTrivialGeometryShader(); +} + +size_t RasterizerOpenGL::CalculateVertexArraysSize() const { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + size_t size = 0; + for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { + if (!regs.vertex_array[index].IsEnabled()) + continue; + + const Tegra::GPUVAddr start = regs.vertex_array[index].StartAddress(); + const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + + ASSERT(end > start); + size += end - start + 1; + } + + return size; +} + +bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { + accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; + DrawArrays(); + return true; +} + +void RasterizerOpenGL::DrawArrays() { + if (accelerate_draw == AccelDraw::Disabled) + return; + + MICROPROFILE_SCOPE(OpenGL_Drawing); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + // TODO(bunnei): Implement these + const bool has_stencil = false; + const bool using_color_fb = true; + const bool using_depth_fb = false; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; + + const bool write_color_fb = + state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || + state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; + + const bool write_depth_fb = + (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) || + (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0); + + Surface color_surface; + Surface depth_surface; + MathUtil::Rectangle<u32> surfaces_rect; + std::tie(color_surface, depth_surface, surfaces_rect) = + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect); + + const u16 res_scale = color_surface != nullptr + ? color_surface->res_scale + : (depth_surface == nullptr ? 1u : depth_surface->res_scale); + + MathUtil::Rectangle<u32> draw_rect{ + static_cast<u32>( + std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Left + static_cast<u32>( + std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top * res_scale, + surfaces_rect.bottom, surfaces_rect.top)), // Top + static_cast<u32>( + std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Right + static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + + viewport_rect.bottom * res_scale, + surfaces_rect.bottom, surfaces_rect.top))}; // Bottom + + // Bind the framebuffer surfaces + BindFramebufferSurfaces(color_surface, depth_surface, has_stencil); + + // Sync the viewport + SyncViewport(surfaces_rect, res_scale); + + // Sync the blend state registers + SyncBlendState(); + + // TODO(bunnei): Sync framebuffer_scale uniform here + // TODO(bunnei): Sync scissorbox uniform(s) here + + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable + // scissor test to prevent drawing outside of the framebuffer region + state.scissor.enabled = true; + state.scissor.x = draw_rect.left; + state.scissor.y = draw_rect.bottom; + state.scissor.width = draw_rect.GetWidth(); + state.scissor.height = draw_rect.GetHeight(); + state.Apply(); + + // Draw the vertex batch + const bool is_indexed = accelerate_draw == AccelDraw::Indexed; + const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()}; + const unsigned vertex_num{is_indexed ? regs.index_array.count : regs.vertex_buffer.count}; + + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + size_t buffer_size = CalculateVertexArraysSize(); + + if (is_indexed) { + buffer_size = Common::AlignUp<size_t>(buffer_size, 4) + index_buffer_size; + } + + // Uniform space for the 5 shader stages + buffer_size = Common::AlignUp<size_t>(buffer_size, 4) + + sizeof(GLShader::MaxwellUniformData) * Maxwell::MaxShaderStage; + + u8* buffer_ptr; + GLintptr buffer_offset; + std::tie(buffer_ptr, buffer_offset) = + stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4); + + u8* offseted_buffer; + std::tie(offseted_buffer, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset); + + offseted_buffer = + reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4)); + buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4); + + // If indexed mode, copy the index buffer + GLintptr index_buffer_offset = 0; + if (is_indexed) { + const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager; + const boost::optional<VAddr> index_data_addr{ + memory_manager->GpuToCpuAddress(regs.index_array.StartAddress())}; + Memory::ReadBlock(*index_data_addr, offseted_buffer, index_buffer_size); + + index_buffer_offset = buffer_offset; + offseted_buffer += index_buffer_size; + buffer_offset += index_buffer_size; + } + + offseted_buffer = + reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4)); + buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4); + + SetupShaders(offseted_buffer, buffer_offset); + + stream_buffer->Unmap(); + + shader_program_manager->ApplyTo(state); + state.Apply(); + + const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)}; + if (is_indexed) { + const GLint index_min{static_cast<GLint>(regs.index_array.first)}; + const GLint index_max{static_cast<GLint>(regs.index_array.first + regs.index_array.count)}; + glDrawRangeElementsBaseVertex(primitive_mode, index_min, index_max, regs.index_array.count, + MaxwellToGL::IndexFormat(regs.index_array.format), + reinterpret_cast<const void*>(index_buffer_offset), + -index_min); + } else { + glDrawArrays(primitive_mode, 0, regs.vertex_buffer.count); + } + + // Disable scissor test + state.scissor.enabled = false; + + accelerate_draw = AccelDraw::Disabled; + + // Unbind textures for potential future use as framebuffer attachments + for (auto& texture_unit : state.texture_units) { + texture_unit.texture_2d = 0; + } + state.Apply(); + + // Mark framebuffer surfaces as dirty + MathUtil::Rectangle<u32> draw_rect_unscaled{ + draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale, + draw_rect.bottom / res_scale}; + + if (color_surface != nullptr && write_color_fb) { + auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + color_surface); + } + if (depth_surface != nullptr && write_depth_fb) { + auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + depth_surface); + } +} + +void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {} + +void RasterizerOpenGL::FlushAll() { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushAll(); +} + +void RasterizerOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushRegion(addr, size); +} + +void RasterizerOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.InvalidateRegion(addr, size, nullptr); +} + +void RasterizerOpenGL::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushRegion(addr, size); + res_cache.InvalidateRegion(addr, size, nullptr); +} + +bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { + MICROPROFILE_SCOPE(OpenGL_Blits); + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) { + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateFill(const void* config) { + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, + ScreenInfo& screen_info) { + if (framebuffer_addr == 0) { + return false; + } + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + + SurfaceParams src_params; + src_params.cpu_addr = framebuffer_addr; + src_params.addr = res_cache.TryFindFramebufferGpuAddress(framebuffer_addr).get_value_or(0); + src_params.width = std::min(framebuffer.width, pixel_stride); + src_params.height = framebuffer.height; + src_params.stride = pixel_stride; + src_params.is_tiled = true; + src_params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight; + src_params.pixel_format = + SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format); + src_params.component_type = + SurfaceParams::ComponentTypeFromGPUPixelFormat(framebuffer.pixel_format); + src_params.UpdateParams(); + + MathUtil::Rectangle<u32> src_rect; + Surface src_surface; + std::tie(src_surface, src_rect) = + res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true); + + if (src_surface == nullptr) { + return false; + } + + u32 scaled_width = src_surface->GetScaledWidth(); + u32 scaled_height = src_surface->GetScaledHeight(); + + screen_info.display_texcoords = MathUtil::Rectangle<float>( + (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width, + (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width); + + screen_info.display_texture = src_surface->texture.handle; + + return true; +} + +void RasterizerOpenGL::SamplerInfo::Create() { + sampler.Create(); + mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear; + wrap_u = wrap_v = Tegra::Texture::WrapMode::Wrap; + border_color_r = border_color_g = border_color_b = border_color_a = 0; + + // default is GL_LINEAR_MIPMAP_LINEAR + glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + // Other attributes have correct defaults +} + +void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { + GLuint s = sampler.handle; + + if (mag_filter != config.mag_filter) { + mag_filter = config.mag_filter; + glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, MaxwellToGL::TextureFilterMode(mag_filter)); + } + if (min_filter != config.min_filter) { + min_filter = config.min_filter; + glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, MaxwellToGL::TextureFilterMode(min_filter)); + } + + if (wrap_u != config.wrap_u) { + wrap_u = config.wrap_u; + glSamplerParameteri(s, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u)); + } + if (wrap_v != config.wrap_v) { + wrap_v = config.wrap_v; + glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v)); + } + + if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border) { + // TODO(Subv): Implement border color + ASSERT(false); + } +} + +u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint program, + u32 current_bindpoint, + const std::vector<GLShader::ConstBufferEntry>& entries) { + auto& gpu = Core::System::GetInstance().GPU(); + auto& maxwell3d = gpu.Get3DEngine(); + + ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage), + "Attempted to upload constbuffer of disabled shader stage"); + + // Reset all buffer draw state for this stage. + for (auto& buffer : state.draw.const_buffers[static_cast<size_t>(stage)]) { + buffer.bindpoint = 0; + buffer.enabled = false; + } + + // Upload only the enabled buffers from the 16 constbuffers of each shader stage + auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)]; + + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& used_buffer = entries[bindpoint]; + const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; + auto& buffer_draw_state = + state.draw.const_buffers[static_cast<size_t>(stage)][used_buffer.GetIndex()]; + + ASSERT_MSG(buffer.enabled, "Attempted to upload disabled constbuffer"); + buffer_draw_state.enabled = true; + buffer_draw_state.bindpoint = current_bindpoint + bindpoint; + + boost::optional<VAddr> addr = gpu.memory_manager->GpuToCpuAddress(buffer.address); + + std::vector<u8> data; + if (used_buffer.IsIndirect()) { + // Buffer is accessed indirectly, so upload the entire thing + data.resize(buffer.size * sizeof(float)); + } else { + // Buffer is accessed directly, upload just what we use + data.resize(used_buffer.GetSize() * sizeof(float)); + } + + Memory::ReadBlock(*addr, data.data(), data.size()); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer_draw_state.ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, data.size(), data.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + // Now configure the bindpoint of the buffer inside the shader + std::string buffer_name = used_buffer.GetName(); + GLuint index = + glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, buffer_name.c_str()); + if (index != -1) + glShaderStorageBlockBinding(program, index, buffer_draw_state.bindpoint); + } + + state.Apply(); + + return current_bindpoint + entries.size(); +} + +u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program, u32 current_unit, + const std::vector<GLShader::SamplerEntry>& entries) { + auto& gpu = Core::System::GetInstance().GPU(); + auto& maxwell3d = gpu.Get3DEngine(); + + ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage), + "Attempted to upload textures of disabled shader stage"); + + ASSERT_MSG(current_unit + entries.size() <= std::size(state.texture_units), + "Exceeded the number of active textures."); + + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + u32 current_bindpoint = current_unit + bindpoint; + + // Bind the uniform to the sampler. + GLint uniform = glGetUniformLocation(program, entry.GetName().c_str()); + ASSERT(uniform != -1); + glProgramUniform1i(program, uniform, current_bindpoint); + + const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset()); + ASSERT(texture.enabled); + + texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc); + Surface surface = res_cache.GetTextureSurface(texture); + if (surface != nullptr) { + state.texture_units[current_bindpoint].texture_2d = surface->texture.handle; + state.texture_units[current_bindpoint].swizzle.r = + MaxwellToGL::SwizzleSource(texture.tic.x_source); + state.texture_units[current_bindpoint].swizzle.g = + MaxwellToGL::SwizzleSource(texture.tic.y_source); + state.texture_units[current_bindpoint].swizzle.b = + MaxwellToGL::SwizzleSource(texture.tic.z_source); + state.texture_units[current_bindpoint].swizzle.a = + MaxwellToGL::SwizzleSource(texture.tic.w_source); + } else { + // Can occur when texture addr is null or its memory is unmapped/invalid + state.texture_units[current_bindpoint].texture_2d = 0; + } + } + + state.Apply(); + + return current_unit + entries.size(); +} + +void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface, + const Surface& depth_surface, bool has_stencil) { + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + color_surface != nullptr ? color_surface->texture.handle : 0, 0); + if (depth_surface != nullptr) { + if (has_stencil) { + // attach both depth and stencil + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + } else { + // attach depth + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + // clear stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + } else { + // clear both depth and stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } +} + +void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; + + state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale; + state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale; + state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale); + state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale); +} + +void RasterizerOpenGL::SyncClipEnabled() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncClipCoef() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncCullMode() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncDepthScale() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncDepthOffset() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncBlendState() { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + // TODO(Subv): Support more than just render target 0. + state.blend.enabled = regs.blend.enable[0] != 0; + + if (!state.blend.enabled) + return; + + ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented"); + ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented"); + state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb); + state.blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_rgb); + state.blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_rgb); + state.blend.a_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_a); + state.blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_a); + state.blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_a); +} diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h new file mode 100644 index 000000000..b7c8cf843 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -0,0 +1,160 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <memory> +#include <vector> +#include <glad/glad.h> +#include "common/common_types.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +struct ScreenInfo; + +class RasterizerOpenGL : public VideoCore::RasterizerInterface { +public: + RasterizerOpenGL(); + ~RasterizerOpenGL() override; + + void DrawArrays() override; + void NotifyMaxwellRegisterChanged(u32 method) override; + void FlushAll() override; + void FlushRegion(Tegra::GPUVAddr addr, u64 size) override; + void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) override; + void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) override; + bool AccelerateDisplayTransfer(const void* config) override; + bool AccelerateTextureCopy(const void* config) override; + bool AccelerateFill(const void* config) override; + bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr, + u32 pixel_stride, ScreenInfo& screen_info) override; + bool AccelerateDrawBatch(bool is_indexed) override; + + /// OpenGL shader generated for a given Maxwell register state + struct MaxwellShader { + /// OpenGL shader resource + OGLProgram shader; + }; + + struct VertexShader { + OGLShader shader; + }; + + struct FragmentShader { + OGLShader shader; + }; + +private: + class SamplerInfo { + public: + OGLSampler sampler; + + /// Creates the sampler object, initializing its state so that it's in sync with the + /// SamplerInfo struct. + void Create(); + /// Syncs the sampler object with the config, updating any necessary state. + void SyncWithConfig(const Tegra::Texture::TSCEntry& config); + + private: + Tegra::Texture::TextureFilter mag_filter; + Tegra::Texture::TextureFilter min_filter; + Tegra::Texture::WrapMode wrap_u; + Tegra::Texture::WrapMode wrap_v; + u32 border_color_r; + u32 border_color_g; + u32 border_color_b; + u32 border_color_a; + }; + + /// Binds the framebuffer color and depth surface + void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface, + bool has_stencil); + + /* + * Configures the current constbuffers to use for the draw command. + * @param stage The shader stage to configure buffers for. + * @param program The OpenGL program object that contains the specified stage. + * @param current_bindpoint The offset at which to start counting new buffer bindpoints. + * @param entries Vector describing the buffers that are actually used in the guest shader. + * @returns The next available bindpoint for use in the next shader stage. + */ + u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program, + u32 current_bindpoint, + const std::vector<GLShader::ConstBufferEntry>& entries); + + /* + * Configures the current textures to use for the draw command. + * @param stage The shader stage to configure textures for. + * @param program The OpenGL program object that contains the specified stage. + * @param current_unit The offset at which to start counting unused texture units. + * @param entries Vector describing the textures that are actually used in the guest shader. + * @returns The next available bindpoint for use in the next shader stage. + */ + u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program, + u32 current_unit, const std::vector<GLShader::SamplerEntry>& entries); + + /// Syncs the viewport to match the guest state + void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale); + + /// Syncs the clip enabled status to match the guest state + void SyncClipEnabled(); + + /// Syncs the clip coefficients to match the guest state + void SyncClipCoef(); + + /// Syncs the cull mode to match the guest state + void SyncCullMode(); + + /// Syncs the depth scale to match the guest state + void SyncDepthScale(); + + /// Syncs the depth offset to match the guest state + void SyncDepthOffset(); + + /// Syncs the blend state to match the guest state + void SyncBlendState(); + + bool has_ARB_buffer_storage; + bool has_ARB_direct_state_access; + bool has_ARB_separate_shader_objects; + bool has_ARB_vertex_attrib_binding; + + OpenGLState state; + + RasterizerCacheOpenGL res_cache; + + std::unique_ptr<GLShader::ProgramManager> shader_program_manager; + OGLVertexArray sw_vao; + OGLVertexArray hw_vao; + + std::array<SamplerInfo, GLShader::NumTextureSamplers> texture_samplers; + std::array<std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers>, + Tegra::Engines::Maxwell3D::Regs::MaxShaderStage> + ssbos; + + static constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; + std::unique_ptr<OGLStreamBuffer> stream_buffer; + OGLBuffer uniform_buffer; + OGLFramebuffer framebuffer; + + size_t CalculateVertexArraysSize() const; + + std::pair<u8*, GLintptr> SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset); + + std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxShaderStage> uniform_buffers; + + void SetupShaders(u8* buffer_ptr, GLintptr buffer_offset); + + enum class AccelDraw { Disabled, Arrays, Indexed }; + AccelDraw accelerate_draw; +}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp new file mode 100644 index 000000000..e61960cc0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -0,0 +1,1418 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <atomic> +#include <cstring> +#include <iterator> +#include <memory> +#include <utility> +#include <vector> +#include <boost/optional.hpp> +#include <boost/range/iterator_range.hpp> +#include <glad/glad.h> +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/frontend/emu_window.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" +#include "core/memory.h" +#include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/textures/decoders.h" +#include "video_core/utils.h" +#include "video_core/video_core.h" + +using SurfaceType = SurfaceParams::SurfaceType; +using PixelFormat = SurfaceParams::PixelFormat; +using ComponentType = SurfaceParams::ComponentType; + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; + bool compressed; +}; + +static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{ + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // ABGR8 + {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, false}, // B5G6R5 + {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, false}, // A2B10G10R10 + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, false}, // A1B5G5R5 + {GL_R8, GL_RED, GL_UNSIGNED_BYTE, false}, // R8 + {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBA16F + {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, false}, // R11FG11FB10F + {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT1 + {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT23 + {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT45 + {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, true}, // DXN1 +}}; + +static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { + const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); + if (type == SurfaceType::ColorTexture) { + ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size()); + // For now only UNORM components are supported, or either R11FG11FB10F or RGBA16F which are + // type FLOAT + ASSERT(component_type == ComponentType::UNorm || pixel_format == PixelFormat::RGBA16F || + pixel_format == PixelFormat::R11FG11FB10F); + return tex_format_tuples[static_cast<unsigned int>(pixel_format)]; + } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { + // TODO(Subv): Implement depth formats + ASSERT_MSG(false, "Unimplemented"); + } + + UNREACHABLE(); + return {}; +} + +template <typename Map, typename Interval> +constexpr auto RangeFromInterval(Map& map, const Interval& interval) { + return boost::make_iterator_range(map.equal_range(interval)); +} + +static u16 GetResolutionScaleFactor() { + return static_cast<u16>(!Settings::values.resolution_factor + ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio() + : Settings::values.resolution_factor); +} + +template <bool morton_to_gl, PixelFormat format> +void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr base, + Tegra::GPUVAddr start, Tegra::GPUVAddr end) { + constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT; + constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); + const auto& gpu = Core::System::GetInstance().GPU(); + + if (morton_to_gl) { + auto data = Tegra::Texture::UnswizzleTexture( + *gpu.memory_manager->GpuToCpuAddress(base), + SurfaceParams::TextureFormatFromPixelFormat(format), stride, height, block_height); + std::memcpy(gl_buffer, data.data(), data.size()); + } else { + // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check + // the configuration for this and perform more generic un/swizzle + NGLOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); + VideoCore::MortonCopyPixels128( + stride, height, bytes_per_pixel, gl_bytes_per_pixel, + Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(base)), gl_buffer, + morton_to_gl); + } +} + +static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr, + Tegra::GPUVAddr), + SurfaceParams::MaxPixelFormat> + morton_to_gl_fns = { + MortonCopy<true, PixelFormat::ABGR8>, MortonCopy<true, PixelFormat::B5G6R5>, + MortonCopy<true, PixelFormat::A2B10G10R10>, MortonCopy<true, PixelFormat::A1B5G5R5>, + MortonCopy<true, PixelFormat::R8>, MortonCopy<true, PixelFormat::RGBA16F>, + MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::DXT1>, + MortonCopy<true, PixelFormat::DXT23>, MortonCopy<true, PixelFormat::DXT45>, + MortonCopy<true, PixelFormat::DXN1>, +}; + +static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr, + Tegra::GPUVAddr), + SurfaceParams::MaxPixelFormat> + gl_to_morton_fns = { + MortonCopy<false, PixelFormat::ABGR8>, + MortonCopy<false, PixelFormat::B5G6R5>, + MortonCopy<false, PixelFormat::A2B10G10R10>, + MortonCopy<false, PixelFormat::A1B5G5R5>, + MortonCopy<false, PixelFormat::R8>, + MortonCopy<false, PixelFormat::RGBA16F>, + MortonCopy<false, PixelFormat::R11FG11FB10F>, + // TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1 formats is not yet supported + nullptr, + nullptr, + nullptr, + nullptr, +}; + +// Allocate an uninitialized texture of appropriate size and format for the surface +static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, + u32 height) { + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Keep track of previous texture bindings + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = texture; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + if (!format_tuple.compressed) { + // Only pre-create the texture for non-compressed textures. + glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, + format_tuple.format, format_tuple.type, nullptr); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Restore previous texture bindings + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); +} + +static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex, + const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type, + GLuint read_fb_handle, GLuint draw_fb_handle) { + + glCopyImageSubData(src_tex, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, dst_tex, + GL_TEXTURE_2D, 0, dst_rect.left, dst_rect.bottom, 0, src_rect.GetWidth(), + src_rect.GetHeight(), 0); + return true; +} + +static bool FillSurface(const Surface& surface, const u8* fill_data, + const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) { + UNREACHABLE(); + return {}; +} + +SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const { + SurfaceParams params = *this; + const u32 tiled_size = is_tiled ? 8 : 1; + const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size); + Tegra::GPUVAddr aligned_start = + addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes); + Tegra::GPUVAddr aligned_end = + addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes); + + if (aligned_end - aligned_start > stride_tiled_bytes) { + params.addr = aligned_start; + params.height = static_cast<u32>((aligned_end - aligned_start) / BytesInPixels(stride)); + } else { + // 1 row + ASSERT(aligned_end - aligned_start == stride_tiled_bytes); + const u64 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1); + aligned_start = + addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment); + aligned_end = + addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment); + params.addr = aligned_start; + params.width = static_cast<u32>(PixelsInBytes(aligned_end - aligned_start) / tiled_size); + params.stride = params.width; + params.height = tiled_size; + } + params.UpdateParams(); + + return params; +} + +SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const { + if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) { + return {}; + } + + if (is_tiled) { + unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8; + unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8; + unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8; + unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8; + } + + const u32 stride_tiled = !is_tiled ? stride : stride * 8; + + const u32 pixel_offset = + stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) + + unscaled_rect.left; + + const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth(); + + return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)}; +} + +MathUtil::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const { + const u32 begin_pixel_index = static_cast<u32>(PixelsInBytes(sub_surface.addr - addr)); + + if (is_tiled) { + const int x0 = (begin_pixel_index % (stride * 8)) / 8; + const int y0 = (begin_pixel_index / (stride * 8)) * 8; + // Top to bottom + return MathUtil::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width, + height - (y0 + sub_surface.height)); + } + + const int x0 = begin_pixel_index % stride; + const int y0 = begin_pixel_index / stride; + // Bottom to top + return MathUtil::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0); +} + +MathUtil::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const { + auto rect = GetSubRect(sub_surface); + rect.left = rect.left * res_scale; + rect.right = rect.right * res_scale; + rect.top = rect.top * res_scale; + rect.bottom = rect.bottom * res_scale; + return rect; +} + +bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const { + return std::tie(other_surface.addr, other_surface.width, other_surface.height, + other_surface.stride, other_surface.block_height, other_surface.pixel_format, + other_surface.component_type, + other_surface.is_tiled) == std::tie(addr, width, height, stride, block_height, + pixel_format, component_type, is_tiled) && + pixel_format != PixelFormat::Invalid; +} + +bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const { + return sub_surface.addr >= addr && sub_surface.end <= end && + sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid && + sub_surface.is_tiled == is_tiled && sub_surface.block_height == block_height && + sub_surface.component_type == component_type && + (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && + (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) && + GetSubRect(sub_surface).left + sub_surface.width <= stride; +} + +bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const { + return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format && + addr <= expanded_surface.end && expanded_surface.addr <= end && + is_tiled == expanded_surface.is_tiled && block_height == expanded_surface.block_height && + component_type == expanded_surface.component_type && stride == expanded_surface.stride && + (std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) % + BytesInPixels(stride * (is_tiled ? 8 : 1)) == + 0; +} + +bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const { + if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr || + end < texcopy_params.end) { + return false; + } + if (texcopy_params.block_height != block_height || + texcopy_params.component_type != component_type) + return false; + + if (texcopy_params.width != texcopy_params.stride) { + const u32 tile_stride = static_cast<u32>(BytesInPixels(stride * (is_tiled ? 8 : 1))); + return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && + texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 && + (texcopy_params.height == 1 || texcopy_params.stride == tile_stride) && + ((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride; + } + return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval(); +} + +VAddr SurfaceParams::GetCpuAddr() const { + // When this function is used, only cpu_addr or (GPU) addr should be set, not both + ASSERT(!(cpu_addr && addr)); + const auto& gpu = Core::System::GetInstance().GPU(); + return cpu_addr.get_value_or(*gpu.memory_manager->GpuToCpuAddress(addr)); +} + +bool CachedSurface::CanFill(const SurfaceParams& dest_surface, + SurfaceInterval fill_interval) const { + if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && + boost::icl::first(fill_interval) >= addr && + boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range + dest_surface.FromInterval(fill_interval).GetInterval() == + fill_interval) { // make sure interval is a rectangle in dest surface + if (fill_size * CHAR_BIT != dest_surface.GetFormatBpp()) { + // Check if bits repeat for our fill_size + const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / CHAR_BIT, 1u); + std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel); + + for (u32 i = 0; i < dest_bytes_per_pixel; ++i) + std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size); + + for (u32 i = 0; i < fill_size; ++i) + if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0], + dest_bytes_per_pixel) != 0) + return false; + + if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) + return false; + } + return true; + } + return false; +} + +bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, + SurfaceInterval copy_interval) const { + SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); + ASSERT(subrect_params.GetInterval() == copy_interval); + if (CanSubRect(subrect_params)) + return true; + + if (CanFill(dest_surface, copy_interval)) + return true; + + return false; +} + +SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const { + SurfaceInterval result{}; + const auto valid_regions = + SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions; + for (auto& valid_interval : valid_regions) { + const SurfaceInterval aligned_interval{ + addr + Common::AlignUp(boost::icl::first(valid_interval) - addr, + BytesInPixels(is_tiled ? 8 * 8 : 1)), + addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr, + BytesInPixels(is_tiled ? 8 * 8 : 1))}; + + if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) || + boost::icl::length(aligned_interval) == 0) { + continue; + } + + // Get the rectangle within aligned_interval + const u32 stride_bytes = static_cast<u32>(BytesInPixels(stride)) * (is_tiled ? 8 : 1); + SurfaceInterval rect_interval{ + addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes), + addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes), + }; + if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) { + // 1 row + rect_interval = aligned_interval; + } else if (boost::icl::length(rect_interval) == 0) { + // 2 rows that do not make a rectangle, return the larger one + const SurfaceInterval row1{boost::icl::first(aligned_interval), + boost::icl::first(rect_interval)}; + const SurfaceInterval row2{boost::icl::first(rect_interval), + boost::icl::last_next(aligned_interval)}; + rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2; + } + + if (boost::icl::length(rect_interval) > boost::icl::length(result)) { + result = rect_interval; + } + } + return result; +} + +void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, + SurfaceInterval copy_interval) { + SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval); + ASSERT(subrect_params.GetInterval() == copy_interval); + + ASSERT(src_surface != dst_surface); + + // This is only called when CanCopy is true, no need to run checks here + if (src_surface->type == SurfaceType::Fill) { + // FillSurface needs a 4 bytes buffer + const u64 fill_offset = + (boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size; + std::array<u8, 4> fill_buffer; + + u64 fill_buff_pos = fill_offset; + for (int i : {0, 1, 2, 3}) + fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size]; + + FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params), + draw_framebuffer.handle); + return; + } + if (src_surface->CanSubRect(subrect_params)) { + BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params), + dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params), + src_surface->type, read_framebuffer.handle, draw_framebuffer.handle); + return; + } + UNREACHABLE(); +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); +void CachedSurface::LoadGLBuffer(Tegra::GPUVAddr load_start, Tegra::GPUVAddr load_end) { + ASSERT(type != SurfaceType::Fill); + + u8* const texture_src_data = Memory::GetPointer(GetCpuAddr()); + if (texture_src_data == nullptr) + return; + + if (gl_buffer == nullptr) { + gl_buffer_size = GetActualWidth() * GetActualHeight() * GetGLBytesPerPixel(pixel_format); + gl_buffer.reset(new u8[gl_buffer_size]); + } + + MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); + + ASSERT(load_start >= addr && load_end <= end); + const u64 start_offset = load_start - addr; + + if (!is_tiled) { + const u32 bytes_per_pixel{GetFormatBpp() >> 3}; + + std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset, + bytes_per_pixel * width * height); + } else { + morton_to_gl_fns[static_cast<size_t>(pixel_format)](GetActualWidth(), block_height, + GetActualHeight(), &gl_buffer[0], addr, + load_start, load_end); + } +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); +void CachedSurface::FlushGLBuffer(Tegra::GPUVAddr flush_start, Tegra::GPUVAddr flush_end) { + u8* const dst_buffer = Memory::GetPointer(GetCpuAddr()); + if (dst_buffer == nullptr) + return; + + ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); + + // TODO: Should probably be done in ::Memory:: and check for other regions too + // same as loadglbuffer() + if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END) + flush_end = Memory::VRAM_VADDR_END; + + if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR) + flush_start = Memory::VRAM_VADDR; + + MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); + + ASSERT(flush_start >= addr && flush_end <= end); + const u64 start_offset = flush_start - addr; + const u64 end_offset = flush_end - addr; + + if (type == SurfaceType::Fill) { + const u64 coarse_start_offset = start_offset - (start_offset % fill_size); + const u64 backup_bytes = start_offset % fill_size; + std::array<u8, 4> backup_data; + if (backup_bytes) + std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes); + + for (u64 offset = coarse_start_offset; offset < end_offset; offset += fill_size) { + std::memcpy(&dst_buffer[offset], &fill_data[0], + std::min(fill_size, end_offset - offset)); + } + + if (backup_bytes) + std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes); + } else if (!is_tiled) { + std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], flush_end - flush_start); + } else { + gl_to_morton_fns[static_cast<size_t>(pixel_format)]( + stride, block_height, height, &gl_buffer[0], addr, flush_start, flush_end); + } +} + +MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); +void CachedSurface::UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle) { + if (type == SurfaceType::Fill) + return; + + MICROPROFILE_SCOPE(OpenGL_TextureUL); + + ASSERT(gl_buffer_size == + GetActualWidth() * GetActualHeight() * GetGLBytesPerPixel(pixel_format)); + + // Load data from memory to the surface + GLint x0 = static_cast<GLint>(rect.left); + GLint y0 = static_cast<GLint>(rect.bottom); + size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format); + + const FormatTuple& tuple = GetFormatTuple(pixel_format, component_type); + GLuint target_tex = texture.handle; + + // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in + // surface + OGLTexture unscaled_tex; + if (res_scale != 1) { + x0 = 0; + y0 = 0; + + unscaled_tex.Create(); + AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + target_tex = unscaled_tex.handle; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = target_tex; + cur_state.Apply(); + + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT + ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride)); + + glActiveTexture(GL_TEXTURE0); + if (tuple.compressed) { + glCompressedTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, + static_cast<GLsizei>(rect.GetWidth() * GetCompresssionFactor()), + static_cast<GLsizei>(rect.GetHeight() * GetCompresssionFactor()), 0, + size, &gl_buffer[buffer_offset]); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()), + static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); + + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle, + scaled_rect, type, read_fb_handle, draw_fb_handle); + } +} + +MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); +void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle) { + if (type == SurfaceType::Fill) + return; + + MICROPROFILE_SCOPE(OpenGL_TextureDL); + + if (gl_buffer == nullptr) { + gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); + gl_buffer.reset(new u8[gl_buffer_size]); + } + + OpenGLState state = OpenGLState::GetCurState(); + OpenGLState prev_state = state; + SCOPE_EXIT({ prev_state.Apply(); }); + + const FormatTuple& tuple = GetFormatTuple(pixel_format, component_type); + + // Ensure no bad interactions with GL_PACK_ALIGNMENT + ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride)); + size_t buffer_offset = (rect.bottom * stride + rect.left) * GetGLBytesPerPixel(pixel_format); + + // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + OGLTexture unscaled_tex; + unscaled_tex.Create(); + + MathUtil::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; + AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type, + read_fb_handle, draw_fb_handle); + + state.texture_units[0].texture_2d = unscaled_tex.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); + } else { + state.ResetTexture(texture.handle); + state.draw.read_framebuffer = read_fb_handle; + state.Apply(); + + if (type == SurfaceType::ColorTexture) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + texture.handle, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); + } else if (type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + texture.handle, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } else { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + texture.handle, 0); + } + glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom), + static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), + tuple.format, tuple.type, &gl_buffer[buffer_offset]); + } + + glPixelStorei(GL_PACK_ROW_LENGTH, 0); +} + +enum class MatchFlags { + None = 0, + Invalid = 1, // Flag that can be applied to other match types, invalid matches require + // validation before they can be used + Exact = 1 << 1, // Surfaces perfectly match + SubRect = 1 << 2, // Surface encompasses params + Copy = 1 << 3, // Surface we can copy from + Expand = 1 << 4, // Surface that can expand params + TexCopy = 1 << 5 // Surface that will match a display transfer "texture copy" parameters +}; + +constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) { + return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs)); +} + +constexpr MatchFlags operator&(MatchFlags lhs, MatchFlags rhs) { + return static_cast<MatchFlags>(static_cast<int>(lhs) & static_cast<int>(rhs)); +} + +/// Get the best surface match (and its match type) for the given flags +template <MatchFlags find_flags> +Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params, + ScaleMatch match_scale_type, + boost::optional<SurfaceInterval> validate_interval = boost::none) { + Surface match_surface = nullptr; + bool match_valid = false; + u32 match_scale = 0; + SurfaceInterval match_interval{}; + + for (auto& pair : RangeFromInterval(surface_cache, params.GetInterval())) { + for (auto& surface : pair.second) { + bool res_scale_matched = match_scale_type == ScaleMatch::Exact + ? (params.res_scale == surface->res_scale) + : (params.res_scale <= surface->res_scale); + // validity will be checked in GetCopyableInterval + bool is_valid = + (find_flags & MatchFlags::Copy) != MatchFlags::None + ? true + : surface->IsRegionValid(validate_interval.value_or(params.GetInterval())); + + if ((find_flags & MatchFlags::Invalid) == MatchFlags::None && !is_valid) + continue; + + auto IsMatch_Helper = [&](auto check_type, auto match_fn) { + if ((find_flags & check_type) == MatchFlags::None) + return; + + bool matched; + SurfaceInterval surface_interval; + std::tie(matched, surface_interval) = match_fn(); + if (!matched) + return; + + if (!res_scale_matched && match_scale_type != ScaleMatch::Ignore && + surface->type != SurfaceType::Fill) + return; + + // Found a match, update only if this is better than the previous one + auto UpdateMatch = [&] { + match_surface = surface; + match_valid = is_valid; + match_scale = surface->res_scale; + match_interval = surface_interval; + }; + + if (surface->res_scale > match_scale) { + UpdateMatch(); + return; + } else if (surface->res_scale < match_scale) { + return; + } + + if (is_valid && !match_valid) { + UpdateMatch(); + return; + } else if (is_valid != match_valid) { + return; + } + + if (boost::icl::length(surface_interval) > boost::icl::length(match_interval)) { + UpdateMatch(); + } + }; + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Exact>{}, [&] { + return std::make_pair(surface->ExactMatch(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::SubRect>{}, [&] { + return std::make_pair(surface->CanSubRect(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Copy>{}, [&] { + auto copy_interval = + params.FromInterval(*validate_interval).GetCopyableInterval(surface); + bool matched = boost::icl::length(copy_interval & *validate_interval) != 0 && + surface->CanCopy(params, copy_interval); + return std::make_pair(matched, copy_interval); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] { + return std::make_pair(surface->CanExpand(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] { + return std::make_pair(surface->CanTexCopy(params), surface->GetInterval()); + }); + } + } + return match_surface; +} + +RasterizerCacheOpenGL::RasterizerCacheOpenGL() { + read_framebuffer.Create(); + draw_framebuffer.Create(); + + attributeless_vao.Create(); + + d24s8_abgr_buffer.Create(); + d24s8_abgr_buffer_size = 0; + + const char* vs_source = R"( +#version 330 core +const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)); +void main() { + gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); +} +)"; + const char* fs_source = R"( +#version 330 core + +uniform samplerBuffer tbo; +uniform vec2 tbo_size; +uniform vec4 viewport; + +out vec4 color; + +void main() { + vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw; + int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x); + color = texelFetch(tbo, tbo_offset).rabg; +} +)"; + d24s8_abgr_shader.CreateFromSource(vs_source, nullptr, fs_source); + + OpenGLState state = OpenGLState::GetCurState(); + GLuint old_program = state.draw.shader_program; + state.draw.shader_program = d24s8_abgr_shader.handle; + state.Apply(); + + GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo"); + ASSERT(tbo_u_id != -1); + glUniform1i(tbo_u_id, 0); + + state.draw.shader_program = old_program; + state.Apply(); + + d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size"); + ASSERT(d24s8_abgr_tbo_size_u_id != -1); + d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport"); + ASSERT(d24s8_abgr_viewport_u_id != -1); +} + +RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); +} + +bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, + const MathUtil::Rectangle<u32>& src_rect, + const Surface& dst_surface, + const MathUtil::Rectangle<u32>& dst_rect) { + if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) + return false; + + return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, + dst_rect, src_surface->type, read_framebuffer.handle, + draw_framebuffer.handle); +} + +void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex, + const MathUtil::Rectangle<u32>& src_rect, + GLuint dst_tex, + const MathUtil::Rectangle<u32>& dst_rect) { + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.draw.read_framebuffer = read_framebuffer.handle; + state.draw.draw_framebuffer = draw_framebuffer.handle; + state.Apply(); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); + + GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4; + if (target_pbo_size > d24s8_abgr_buffer_size) { + d24s8_abgr_buffer_size = target_pbo_size * 2; + glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY); + } + + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, + 0); + glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom), + static_cast<GLsizei>(src_rect.GetWidth()), + static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + 0); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + // PBO now contains src_tex in RABG format + state.draw.shader_program = d24s8_abgr_shader.handle; + state.draw.vertex_array = attributeless_vao.handle; + state.viewport.x = static_cast<GLint>(dst_rect.left); + state.viewport.y = static_cast<GLint>(dst_rect.bottom); + state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth()); + state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight()); + state.Apply(); + + OGLTexture tbo; + tbo.Create(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_BUFFER, tbo.handle); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle); + + glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()), + static_cast<GLfloat>(src_rect.GetHeight())); + glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x), + static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width), + static_cast<GLfloat>(state.viewport.height)); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindTexture(GL_TEXTURE_BUFFER, 0); +} + +Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return nullptr; + } + // Use GetSurfaceSubRect instead + ASSERT(params.width == params.stride); + + // Check for an exact match in existing surfaces + Surface surface = + FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale); + + if (surface == nullptr) { + u16 target_res_scale = params.res_scale; + if (match_res_scale != ScaleMatch::Exact) { + // This surface may have a subrect of another surface with a higher res_scale, find it + // to adjust our params + SurfaceParams find_params = params; + Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( + surface_cache, find_params, match_res_scale); + if (expandable != nullptr && expandable->res_scale > target_res_scale) { + target_res_scale = expandable->res_scale; + } + } + SurfaceParams new_params = params; + new_params.res_scale = target_res_scale; + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + + if (load_if_create) { + ValidateSurface(surface, params.addr, params.size); + } + + return surface; +} + +boost::optional<Tegra::GPUVAddr> RasterizerCacheOpenGL::TryFindFramebufferGpuAddress( + VAddr cpu_addr) const { + // Tries to find the GPU address of a framebuffer based on the CPU address. This is because + // final output framebuffers are specified by CPU address, but internally our GPU cache uses GPU + // addresses. We iterate through all cached framebuffers, and compare their starting CPU address + // to the one provided. This is obviously not great, and won't work if the framebuffer overlaps + // surfaces. + + std::vector<Tegra::GPUVAddr> gpu_addresses; + for (const auto& pair : surface_cache) { + for (const auto& surface : pair.second) { + const VAddr surface_cpu_addr = surface->GetCpuAddr(); + if (cpu_addr >= surface_cpu_addr && cpu_addr < (surface_cpu_addr + surface->size)) { + ASSERT_MSG(cpu_addr == surface_cpu_addr, "overlapping surfaces are unsupported"); + gpu_addresses.push_back(surface->addr); + } + } + } + + if (gpu_addresses.empty()) { + return {}; + } + + ASSERT_MSG(gpu_addresses.size() == 1, ">1 surface is unsupported"); + return gpu_addresses[0]; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params, + ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return std::make_tuple(nullptr, MathUtil::Rectangle<u32>{}); + } + + // Attempt to find encompassing surface + Surface surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, + match_res_scale); + + // Check if FindMatch failed because of res scaling + // If that's the case create a new surface with + // the dimensions of the lower res_scale surface + // to suggest it should not be used again + if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) { + surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, + ScaleMatch::Ignore); + if (surface != nullptr) { + ASSERT(surface->res_scale < params.res_scale); + SurfaceParams new_params = *surface; + new_params.res_scale = params.res_scale; + + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + } + + SurfaceParams aligned_params = params; + if (params.is_tiled) { + aligned_params.height = Common::AlignUp(params.height, 8); + aligned_params.width = Common::AlignUp(params.width, 8); + aligned_params.stride = Common::AlignUp(params.stride, 8); + aligned_params.UpdateParams(); + } + + // Check for a surface we can expand before creating a new one + if (surface == nullptr) { + surface = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(surface_cache, aligned_params, + match_res_scale); + if (surface != nullptr) { + aligned_params.width = aligned_params.stride; + aligned_params.UpdateParams(); + + SurfaceParams new_params = *surface; + new_params.addr = std::min(aligned_params.addr, surface->addr); + new_params.end = std::max(aligned_params.end, surface->end); + new_params.size = new_params.end - new_params.addr; + new_params.height = static_cast<u32>( + new_params.size / aligned_params.BytesInPixels(aligned_params.stride)); + ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0); + + Surface new_surface = CreateSurface(new_params); + DuplicateSurface(surface, new_surface); + + // Delete the expanded surface, this can't be done safely yet + // because it may still be in use + remove_surfaces.emplace(surface); + + surface = new_surface; + RegisterSurface(new_surface); + } + } + + // No subrect found - create and return a new surface + if (surface == nullptr) { + SurfaceParams new_params = aligned_params; + // Can't have gaps in a surface + new_params.width = aligned_params.stride; + new_params.UpdateParams(); + // GetSurface will create the new surface and possibly adjust res_scale if necessary + surface = GetSurface(new_params, match_res_scale, load_if_create); + } else if (load_if_create) { + ValidateSurface(surface, aligned_params.addr, aligned_params.size); + } + + return std::make_tuple(surface, surface->GetScaledSubRect(params)); +} + +Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) { + auto& gpu = Core::System::GetInstance().GPU(); + + SurfaceParams params; + params.addr = config.tic.Address(); + params.is_tiled = config.tic.IsTiled(); + params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(config.tic.format); + + params.width = Common::AlignUp(config.tic.Width(), params.GetCompresssionFactor()) / + params.GetCompresssionFactor(); + params.height = Common::AlignUp(config.tic.Height(), params.GetCompresssionFactor()) / + params.GetCompresssionFactor(); + + // TODO(Subv): Different types per component are not supported. + ASSERT(config.tic.r_type.Value() == config.tic.g_type.Value() && + config.tic.r_type.Value() == config.tic.b_type.Value() && + config.tic.r_type.Value() == config.tic.a_type.Value()); + + params.component_type = SurfaceParams::ComponentTypeFromTexture(config.tic.r_type.Value()); + + if (config.tic.IsTiled()) { + params.block_height = config.tic.BlockHeight(); + + // TODO(bunnei): The below align up is a hack. This is here because some compressed textures + // are not a multiple of their own compression factor, and so this accounts for that. This + // could potentially result in an extra row of 4px being decoded if a texture is not a + // multiple of 4. + params.width = Common::AlignUp(params.width, 4); + params.height = Common::AlignUp(params.height, 4); + } else { + // Use the texture-provided stride value if the texture isn't tiled. + params.stride = static_cast<u32>(params.PixelsInBytes(config.tic.Pitch())); + } + + params.UpdateParams(); + + return GetSurface(params, ScaleMatch::Ignore, true); +} + +SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( + bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const auto& config = regs.rt[0]; + + // TODO(bunnei): This is hard corded to use just the first render buffer + NGLOG_WARNING(Render_OpenGL, "hard-coded for render target 0!"); + + // update resolution_scale_factor and reset cache if changed + // TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We + // need to fix this before making the renderer multi-threaded. + static u16 resolution_scale_factor = GetResolutionScaleFactor(); + if (resolution_scale_factor != GetResolutionScaleFactor()) { + resolution_scale_factor = GetResolutionScaleFactor(); + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); + } + + MathUtil::Rectangle<u32> viewport_clamped{ + static_cast<u32>(std::clamp(viewport.left, 0, static_cast<s32>(config.width))), + static_cast<u32>(std::clamp(viewport.top, 0, static_cast<s32>(config.height))), + static_cast<u32>(std::clamp(viewport.right, 0, static_cast<s32>(config.width))), + static_cast<u32>(std::clamp(viewport.bottom, 0, static_cast<s32>(config.height)))}; + + // get color and depth surfaces + SurfaceParams color_params; + color_params.is_tiled = true; + color_params.res_scale = resolution_scale_factor; + color_params.width = config.width; + color_params.height = config.height; + // TODO(Subv): Can framebuffers use a different block height? + color_params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight; + SurfaceParams depth_params = color_params; + + color_params.addr = config.Address(); + color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format); + color_params.component_type = SurfaceParams::ComponentTypeFromRenderTarget(config.format); + color_params.UpdateParams(); + + ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented"); + // depth_params.addr = config.GetDepthBufferPhysicalAddress(); + // depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format); + // depth_params.UpdateParams(); + + auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); + auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); + + // Make sure that framebuffers don't overlap if both color and depth are being used + if (using_color_fb && using_depth_fb && + boost::icl::length(color_vp_interval & depth_vp_interval)) { + NGLOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " + "overlapping framebuffers not supported!"); + using_depth_fb = false; + } + + MathUtil::Rectangle<u32> color_rect{}; + Surface color_surface = nullptr; + if (using_color_fb) + std::tie(color_surface, color_rect) = + GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> depth_rect{}; + Surface depth_surface = nullptr; + if (using_depth_fb) + std::tie(depth_surface, depth_rect) = + GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> fb_rect{}; + if (color_surface != nullptr && depth_surface != nullptr) { + fb_rect = color_rect; + // Color and Depth surfaces must have the same dimensions and offsets + if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top || + color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) { + color_surface = GetSurface(color_params, ScaleMatch::Exact, false); + depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); + fb_rect = color_surface->GetScaledRect(); + } + } else if (color_surface != nullptr) { + fb_rect = color_rect; + } else if (depth_surface != nullptr) { + fb_rect = depth_rect; + } + + if (color_surface != nullptr) { + ValidateSurface(color_surface, boost::icl::first(color_vp_interval), + boost::icl::length(color_vp_interval)); + } + if (depth_surface != nullptr) { + ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), + boost::icl::length(depth_vp_interval)); + } + + return std::make_tuple(color_surface, depth_surface, fb_rect); +} + +Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) { + UNREACHABLE(); + return {}; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) { + MathUtil::Rectangle<u32> rect{}; + + Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>( + surface_cache, params, ScaleMatch::Ignore); + + if (match_surface != nullptr) { + ValidateSurface(match_surface, params.addr, params.size); + + SurfaceParams match_subrect; + if (params.width != params.stride) { + const u32 tiled_size = match_surface->is_tiled ? 8 : 1; + match_subrect = params; + match_subrect.width = + static_cast<u32>(match_surface->PixelsInBytes(params.width) / tiled_size); + match_subrect.stride = + static_cast<u32>(match_surface->PixelsInBytes(params.stride) / tiled_size); + match_subrect.height *= tiled_size; + } else { + match_subrect = match_surface->FromInterval(params.GetInterval()); + ASSERT(match_subrect.GetInterval() == params.GetInterval()); + } + + rect = match_surface->GetScaledSubRect(match_subrect); + } + + return std::make_tuple(match_surface, rect); +} + +void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, + const Surface& dest_surface) { + ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end); + + BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface, + dest_surface->GetScaledSubRect(*src_surface)); + + dest_surface->invalid_regions -= src_surface->GetInterval(); + dest_surface->invalid_regions += src_surface->invalid_regions; + + SurfaceRegions regions; + for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) { + if (pair.second == src_surface) { + regions += pair.first; + } + } + for (auto& interval : regions) { + dirty_regions.set({interval, dest_surface}); + } +} + +void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, Tegra::GPUVAddr addr, + u64 size) { + if (size == 0) + return; + + const SurfaceInterval validate_interval(addr, addr + size); + + if (surface->type == SurfaceType::Fill) { + // Sanity check, fill surfaces will always be valid when used + ASSERT(surface->IsRegionValid(validate_interval)); + return; + } + + while (true) { + const auto it = surface->invalid_regions.find(validate_interval); + if (it == surface->invalid_regions.end()) + break; + + const auto interval = *it & validate_interval; + // Look for a valid surface to copy from + SurfaceParams params = *surface; + + Surface copy_surface = + FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); + if (copy_surface != nullptr) { + SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); + CopySurface(copy_surface, surface, copy_interval); + surface->invalid_regions.erase(copy_interval); + continue; + } + + // Load data from Switch memory + FlushRegion(params.addr, params.size); + surface->LoadGLBuffer(params.addr, params.end); + surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, + draw_framebuffer.handle); + surface->invalid_regions.erase(params.GetInterval()); + } +} + +void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size, Surface flush_surface) { + if (size == 0) + return; + + const SurfaceInterval flush_interval(addr, addr + size); + SurfaceRegions flushed_intervals; + + for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) { + // small sizes imply that this most likely comes from the cpu, flush the entire region + // the point is to avoid thousands of small writes every frame if the cpu decides to access + // that region, anything higher than 8 you're guaranteed it comes from a service + const auto interval = size <= 8 ? pair.first : pair.first & flush_interval; + auto& surface = pair.second; + + if (flush_surface != nullptr && surface != flush_surface) + continue; + + // Sanity check, this surface is the last one that marked this region dirty + ASSERT(surface->IsRegionValid(interval)); + + if (surface->type != SurfaceType::Fill) { + SurfaceParams params = surface->FromInterval(interval); + surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, + draw_framebuffer.handle); + } + surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval)); + flushed_intervals += interval; + } + // Reset dirty regions + dirty_regions -= flushed_intervals; +} + +void RasterizerCacheOpenGL::FlushAll() { + FlushRegion(0, Kernel::VMManager::MAX_ADDRESS); +} + +void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size, + const Surface& region_owner) { + if (size == 0) + return; + + const SurfaceInterval invalid_interval(addr, addr + size); + + if (region_owner != nullptr) { + ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end); + // Surfaces can't have a gap + ASSERT(region_owner->width == region_owner->stride); + region_owner->invalid_regions.erase(invalid_interval); + } + + for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) { + for (auto& cached_surface : pair.second) { + if (cached_surface == region_owner) + continue; + + // If cpu is invalidating this region we want to remove it + // to (likely) mark the memory pages as uncached + if (region_owner == nullptr && size <= 8) { + FlushRegion(cached_surface->addr, cached_surface->size, cached_surface); + remove_surfaces.emplace(cached_surface); + continue; + } + + const auto interval = cached_surface->GetInterval() & invalid_interval; + cached_surface->invalid_regions.insert(interval); + + // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures + if (cached_surface->type == SurfaceType::Fill && + cached_surface->IsSurfaceFullyInvalid()) { + remove_surfaces.emplace(cached_surface); + } + } + } + + if (region_owner != nullptr) + dirty_regions.set({invalid_interval, region_owner}); + else + dirty_regions.erase(invalid_interval); + + for (auto& remove_surface : remove_surfaces) { + if (remove_surface == region_owner) { + Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>( + surface_cache, *region_owner, ScaleMatch::Ignore); + ASSERT(expanded_surface); + + if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) { + DuplicateSurface(region_owner, expanded_surface); + } else { + continue; + } + } + UnregisterSurface(remove_surface); + } + + remove_surfaces.clear(); +} + +Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) { + Surface surface = std::make_shared<CachedSurface>(); + static_cast<SurfaceParams&>(*surface) = params; + + surface->texture.Create(); + + surface->gl_buffer_size = 0; + surface->invalid_regions.insert(surface->GetInterval()); + AllocateSurfaceTexture(surface->texture.handle, + GetFormatTuple(surface->pixel_format, surface->component_type), + surface->GetScaledWidth(), surface->GetScaledHeight()); + + return surface; +} + +void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) { + if (surface->registered) { + return; + } + surface->registered = true; + surface_cache.add({surface->GetInterval(), SurfaceSet{surface}}); + UpdatePagesCachedCount(surface->addr, surface->size, 1); +} + +void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) { + if (!surface->registered) { + return; + } + surface->registered = false; + UpdatePagesCachedCount(surface->addr, surface->size, -1); + surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}}); +} + +void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) { + const u64 num_pages = ((addr + size - 1) >> Tegra::MemoryManager::PAGE_BITS) - + (addr >> Tegra::MemoryManager::PAGE_BITS) + 1; + const u64 page_start = addr >> Tegra::MemoryManager::PAGE_BITS; + const u64 page_end = page_start + num_pages; + + // Interval maps will erase segments if count reaches 0, so if delta is negative we have to + // subtract after iterating + const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end); + if (delta > 0) + cached_pages.add({pages_interval, delta}); + + for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) { + const auto interval = pair.first & pages_interval; + const int count = pair.second; + + const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval) + << Tegra::MemoryManager::PAGE_BITS; + const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval) + << Tegra::MemoryManager::PAGE_BITS; + const u64 interval_size = interval_end_addr - interval_start_addr; + + if (delta > 0 && count == delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true); + else if (delta < 0 && count == -delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false); + else + ASSERT(count >= 0); + } + + if (delta < 0) + cached_pages.add({pages_interval, delta}); +} diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h new file mode 100644 index 000000000..0f43e863d --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -0,0 +1,520 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <set> +#include <tuple> +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#endif +#include <boost/icl/interval_map.hpp> +#include <boost/icl/interval_set.hpp> +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#include <boost/optional.hpp> +#include <glad/glad.h> +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/textures/texture.h" + +struct CachedSurface; +using Surface = std::shared_ptr<CachedSurface>; +using SurfaceSet = std::set<Surface>; + +using SurfaceRegions = boost::icl::interval_set<Tegra::GPUVAddr>; +using SurfaceMap = boost::icl::interval_map<Tegra::GPUVAddr, Surface>; +using SurfaceCache = boost::icl::interval_map<Tegra::GPUVAddr, SurfaceSet>; + +using SurfaceInterval = SurfaceCache::interval_type; +static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() && + std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(), + "incorrect interval types"); + +using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>; +using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>; + +using PageMap = boost::icl::interval_map<u64, int>; + +enum class ScaleMatch { + Exact, // only accept same res scale + Upscale, // only allow higher scale than params + Ignore // accept every scaled res +}; + +struct SurfaceParams { + enum class PixelFormat { + ABGR8 = 0, + B5G6R5 = 1, + A2B10G10R10 = 2, + A1B5G5R5 = 3, + R8 = 4, + RGBA16F = 5, + R11FG11FB10F = 6, + DXT1 = 7, + DXT23 = 8, + DXT45 = 9, + DXN1 = 10, // This is also known as BC4 + + Max, + Invalid = 255, + }; + + static constexpr size_t MaxPixelFormat = static_cast<size_t>(PixelFormat::Max); + + enum class ComponentType { + Invalid = 0, + SNorm = 1, + UNorm = 2, + SInt = 3, + UInt = 4, + Float = 5, + }; + + enum class SurfaceType { + ColorTexture = 0, + Depth = 1, + DepthStencil = 2, + Fill = 3, + Invalid = 4, + }; + + /** + * Gets the compression factor for the specified PixelFormat. This applies to just the + * "compressed width" and "compressed height", not the overall compression factor of a + * compressed image. This is used for maintaining proper surface sizes for compressed texture + * formats. + */ + static constexpr u32 GetCompresssionFactor(PixelFormat format) { + if (format == PixelFormat::Invalid) + return 0; + + constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ + 1, // ABGR8 + 1, // B5G6R5 + 1, // A2B10G10R10 + 1, // A1B5G5R5 + 1, // R8 + 1, // RGBA16F + 1, // R11FG11FB10F + 4, // DXT1 + 4, // DXT23 + 4, // DXT45 + 4, // DXN1 + }}; + + ASSERT(static_cast<size_t>(format) < compression_factor_table.size()); + return compression_factor_table[static_cast<size_t>(format)]; + } + u32 GetCompresssionFactor() const { + return GetCompresssionFactor(pixel_format); + } + + static constexpr u32 GetFormatBpp(PixelFormat format) { + if (format == PixelFormat::Invalid) + return 0; + + constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ + 32, // ABGR8 + 16, // B5G6R5 + 32, // A2B10G10R10 + 16, // A1B5G5R5 + 8, // R8 + 64, // RGBA16F + 32, // R11FG11FB10F + 64, // DXT1 + 128, // DXT23 + 128, // DXT45 + 64, // DXN1 + }}; + + ASSERT(static_cast<size_t>(format) < bpp_table.size()); + return bpp_table[static_cast<size_t>(format)]; + } + u32 GetFormatBpp() const { + return GetFormatBpp(pixel_format); + } + + static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) { + switch (format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + case Tegra::RenderTargetFormat::RGBA8_SRGB: + return PixelFormat::ABGR8; + case Tegra::RenderTargetFormat::RGB10_A2_UNORM: + return PixelFormat::A2B10G10R10; + case Tegra::RenderTargetFormat::RGBA16_FLOAT: + return PixelFormat::RGBA16F; + case Tegra::RenderTargetFormat::R11G11B10_FLOAT: + return PixelFormat::R11FG11FB10F; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); + UNREACHABLE(); + } + } + + static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) { + switch (format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: + return PixelFormat::ABGR8; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); + UNREACHABLE(); + } + } + + static PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format) { + // TODO(Subv): Properly implement this + switch (format) { + case Tegra::Texture::TextureFormat::A8R8G8B8: + return PixelFormat::ABGR8; + case Tegra::Texture::TextureFormat::B5G6R5: + return PixelFormat::B5G6R5; + case Tegra::Texture::TextureFormat::A2B10G10R10: + return PixelFormat::A2B10G10R10; + case Tegra::Texture::TextureFormat::A1B5G5R5: + return PixelFormat::A1B5G5R5; + case Tegra::Texture::TextureFormat::R8: + return PixelFormat::R8; + case Tegra::Texture::TextureFormat::R16_G16_B16_A16: + return PixelFormat::RGBA16F; + case Tegra::Texture::TextureFormat::BF10GF11RF11: + return PixelFormat::R11FG11FB10F; + case Tegra::Texture::TextureFormat::DXT1: + return PixelFormat::DXT1; + case Tegra::Texture::TextureFormat::DXT23: + return PixelFormat::DXT23; + case Tegra::Texture::TextureFormat::DXT45: + return PixelFormat::DXT45; + case Tegra::Texture::TextureFormat::DXN1: + return PixelFormat::DXN1; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); + UNREACHABLE(); + } + } + + static Tegra::Texture::TextureFormat TextureFormatFromPixelFormat(PixelFormat format) { + // TODO(Subv): Properly implement this + switch (format) { + case PixelFormat::ABGR8: + return Tegra::Texture::TextureFormat::A8R8G8B8; + case PixelFormat::B5G6R5: + return Tegra::Texture::TextureFormat::B5G6R5; + case PixelFormat::A2B10G10R10: + return Tegra::Texture::TextureFormat::A2B10G10R10; + case PixelFormat::A1B5G5R5: + return Tegra::Texture::TextureFormat::A1B5G5R5; + case PixelFormat::R8: + return Tegra::Texture::TextureFormat::R8; + case PixelFormat::RGBA16F: + return Tegra::Texture::TextureFormat::R16_G16_B16_A16; + case PixelFormat::R11FG11FB10F: + return Tegra::Texture::TextureFormat::BF10GF11RF11; + case PixelFormat::DXT1: + return Tegra::Texture::TextureFormat::DXT1; + case PixelFormat::DXT23: + return Tegra::Texture::TextureFormat::DXT23; + case PixelFormat::DXT45: + return Tegra::Texture::TextureFormat::DXT45; + case PixelFormat::DXN1: + return Tegra::Texture::TextureFormat::DXN1; + default: + UNREACHABLE(); + } + } + + static ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) { + // TODO(Subv): Implement more component types + switch (type) { + case Tegra::Texture::ComponentType::UNORM: + return ComponentType::UNorm; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type)); + UNREACHABLE(); + } + } + + static ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) { + // TODO(Subv): Implement more render targets + switch (format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + case Tegra::RenderTargetFormat::RGBA8_SRGB: + case Tegra::RenderTargetFormat::RGB10_A2_UNORM: + return ComponentType::UNorm; + case Tegra::RenderTargetFormat::RGBA16_FLOAT: + case Tegra::RenderTargetFormat::R11G11B10_FLOAT: + return ComponentType::Float; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); + UNREACHABLE(); + } + } + + static ComponentType ComponentTypeFromGPUPixelFormat( + Tegra::FramebufferConfig::PixelFormat format) { + switch (format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: + return ComponentType::UNorm; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); + UNREACHABLE(); + } + } + + static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { + SurfaceType a_type = GetFormatType(pixel_format_a); + SurfaceType b_type = GetFormatType(pixel_format_b); + + if (a_type == SurfaceType::ColorTexture && b_type == SurfaceType::ColorTexture) { + return true; + } + + if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { + return true; + } + + if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { + return true; + } + + return false; + } + + static SurfaceType GetFormatType(PixelFormat pixel_format) { + if (static_cast<size_t>(pixel_format) < MaxPixelFormat) { + return SurfaceType::ColorTexture; + } + + // TODO(Subv): Implement the other formats + ASSERT(false); + + return SurfaceType::Invalid; + } + + /// Update the params "size", "end" and "type" from the already set "addr", "width", "height" + /// and "pixel_format" + void UpdateParams() { + if (stride == 0) { + stride = width; + } + type = GetFormatType(pixel_format); + size = !is_tiled ? BytesInPixels(stride * (height - 1) + width) + : BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8); + end = addr + size; + } + + SurfaceInterval GetInterval() const { + return SurfaceInterval::right_open(addr, end); + } + + // Returns the outer rectangle containing "interval" + SurfaceParams FromInterval(SurfaceInterval interval) const; + + SurfaceInterval GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const; + + // Returns the region of the biggest valid rectange within interval + SurfaceInterval GetCopyableInterval(const Surface& src_surface) const; + + /** + * Gets the actual width (in pixels) of the surface. This is provided because `width` is used + * for tracking the surface region in memory, which may be compressed for certain formats. In + * this scenario, `width` is actually the compressed width. + */ + u32 GetActualWidth() const { + return width * GetCompresssionFactor(); + } + + /** + * Gets the actual height (in pixels) of the surface. This is provided because `height` is used + * for tracking the surface region in memory, which may be compressed for certain formats. In + * this scenario, `height` is actually the compressed height. + */ + u32 GetActualHeight() const { + return height * GetCompresssionFactor(); + } + + u32 GetScaledWidth() const { + return width * res_scale; + } + + u32 GetScaledHeight() const { + return height * res_scale; + } + + MathUtil::Rectangle<u32> GetRect() const { + return {0, height, width, 0}; + } + + MathUtil::Rectangle<u32> GetScaledRect() const { + return {0, GetScaledHeight(), GetScaledWidth(), 0}; + } + + u64 PixelsInBytes(u64 size) const { + return size * CHAR_BIT / GetFormatBpp(pixel_format); + } + + u64 BytesInPixels(u64 pixels) const { + return pixels * GetFormatBpp(pixel_format) / CHAR_BIT; + } + + VAddr GetCpuAddr() const; + + bool ExactMatch(const SurfaceParams& other_surface) const; + bool CanSubRect(const SurfaceParams& sub_surface) const; + bool CanExpand(const SurfaceParams& expanded_surface) const; + bool CanTexCopy(const SurfaceParams& texcopy_params) const; + + MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const; + MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const; + + Tegra::GPUVAddr addr = 0; + Tegra::GPUVAddr end = 0; + boost::optional<VAddr> cpu_addr; + u64 size = 0; + + u32 width = 0; + u32 height = 0; + u32 stride = 0; + u32 block_height = 0; + u16 res_scale = 1; + + bool is_tiled = false; + PixelFormat pixel_format = PixelFormat::Invalid; + SurfaceType type = SurfaceType::Invalid; + ComponentType component_type = ComponentType::Invalid; +}; + +struct CachedSurface : SurfaceParams { + bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; + bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; + + bool IsRegionValid(SurfaceInterval interval) const { + return (invalid_regions.find(interval) == invalid_regions.end()); + } + + bool IsSurfaceFullyInvalid() const { + return (invalid_regions & GetInterval()) == SurfaceRegions(GetInterval()); + } + + bool registered = false; + SurfaceRegions invalid_regions; + + u64 fill_size = 0; /// Number of bytes to read from fill_data + std::array<u8, 4> fill_data; + + OGLTexture texture; + + static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) { + if (format == PixelFormat::Invalid) + return 0; + + return SurfaceParams::GetFormatBpp(format) / CHAR_BIT; + } + + std::unique_ptr<u8[]> gl_buffer; + size_t gl_buffer_size = 0; + + // Read/Write data in Switch memory to/from gl_buffer + void LoadGLBuffer(Tegra::GPUVAddr load_start, Tegra::GPUVAddr load_end); + void FlushGLBuffer(Tegra::GPUVAddr flush_start, Tegra::GPUVAddr flush_end); + + // Upload/Download data in gl_buffer in/to this surface's texture + void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle); + void DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle); +}; + +class RasterizerCacheOpenGL : NonCopyable { +public: + RasterizerCacheOpenGL(); + ~RasterizerCacheOpenGL(); + + /// Blit one surface's texture to another + bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect, + const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect); + + void ConvertD24S8toABGR(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, + GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect); + + /// Copy one surface's region to another + void CopySurface(const Surface& src_surface, const Surface& dst_surface, + SurfaceInterval copy_interval); + + /// Load a texture from Switch memory to OpenGL and cache it (if not already cached) + Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create); + + /// Tries to find a framebuffer GPU address based on the provided CPU address + boost::optional<Tegra::GPUVAddr> TryFindFramebufferGpuAddress(VAddr cpu_addr) const; + + /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from + /// Switch memory to OpenGL and caches it (if not already cached) + SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create); + + /// Get a surface based on the texture configuration + Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config); + + /// Get the color and depth surfaces based on the framebuffer configuration + SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, + const MathUtil::Rectangle<s32>& viewport); + + /// Get a surface that matches the fill config + Surface GetFillSurface(const void* config); + + /// Get a surface that matches a "texture copy" display transfer config + SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); + + /// Write any cached resources overlapping the region back to memory (if dirty) + void FlushRegion(Tegra::GPUVAddr addr, u64 size, Surface flush_surface = nullptr); + + /// Mark region as being invalidated by region_owner (nullptr if Switch memory) + void InvalidateRegion(Tegra::GPUVAddr addr, u64 size, const Surface& region_owner); + + /// Flush all cached resources tracked by this cache manager + void FlushAll(); + +private: + void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface); + + /// Update surface's texture for given region when necessary + void ValidateSurface(const Surface& surface, Tegra::GPUVAddr addr, u64 size); + + /// Create a new surface + Surface CreateSurface(const SurfaceParams& params); + + /// Register surface into the cache + void RegisterSurface(const Surface& surface); + + /// Remove surface from the cache + void UnregisterSurface(const Surface& surface); + + /// Increase/decrease the number of surface in pages touching the specified region + void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta); + + SurfaceCache surface_cache; + PageMap cached_pages; + SurfaceMap dirty_regions; + SurfaceSet remove_surfaces; + + OGLFramebuffer read_framebuffer; + OGLFramebuffer draw_framebuffer; + + OGLVertexArray attributeless_vao; + OGLBuffer d24s8_abgr_buffer; + GLsizeiptr d24s8_abgr_buffer_size; + OGLProgram d24s8_abgr_shader; + GLint d24s8_abgr_tbo_size_u_id; + GLint d24s8_abgr_viewport_u_id; +}; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 13301ec9f..93f9172e7 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -13,14 +13,16 @@ class OGLTexture : private NonCopyable { public: OGLTexture() = default; - OGLTexture(OGLTexture&& o) { - std::swap(handle, o.handle); - } + + OGLTexture(OGLTexture&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLTexture() { Release(); } - OGLTexture& operator=(OGLTexture&& o) { - std::swap(handle, o.handle); + + OGLTexture& operator=(OGLTexture&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } @@ -36,7 +38,7 @@ public: if (handle == 0) return; glDeleteTextures(1, &handle); - OpenGLState::ResetTexture(handle); + OpenGLState::GetCurState().ResetTexture(handle).Apply(); handle = 0; } @@ -46,14 +48,16 @@ public: class OGLSampler : private NonCopyable { public: OGLSampler() = default; - OGLSampler(OGLSampler&& o) { - std::swap(handle, o.handle); - } + + OGLSampler(OGLSampler&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLSampler() { Release(); } - OGLSampler& operator=(OGLSampler&& o) { - std::swap(handle, o.handle); + + OGLSampler& operator=(OGLSampler&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } @@ -69,7 +73,7 @@ public: if (handle == 0) return; glDeleteSamplers(1, &handle); - OpenGLState::ResetSampler(handle); + OpenGLState::GetCurState().ResetSampler(handle).Apply(); handle = 0; } @@ -79,22 +83,71 @@ public: class OGLShader : private NonCopyable { public: OGLShader() = default; - OGLShader(OGLShader&& o) { - std::swap(handle, o.handle); - } + + OGLShader(OGLShader&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLShader() { Release(); } - OGLShader& operator=(OGLShader&& o) { - std::swap(handle, o.handle); + + OGLShader& operator=(OGLShader&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } - /// Creates a new internal OpenGL resource and stores the handle - void Create(const char* vert_shader, const char* frag_shader) { + void Create(const char* source, GLenum type) { if (handle != 0) return; - handle = GLShader::LoadProgram(vert_shader, frag_shader); + if (source == nullptr) + return; + handle = GLShader::LoadShader(source, type); + } + + void Release() { + if (handle == 0) + return; + glDeleteShader(handle); + handle = 0; + } + + GLuint handle = 0; +}; + +class OGLProgram : private NonCopyable { +public: + OGLProgram() = default; + + OGLProgram(OGLProgram&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + + ~OGLProgram() { + Release(); + } + + OGLProgram& operator=(OGLProgram&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); + return *this; + } + + template <typename... T> + void Create(bool separable_program, T... shaders) { + if (handle != 0) + return; + handle = GLShader::LoadProgram(separable_program, shaders...); + } + + /// Creates a new internal OpenGL resource and stores the handle + void CreateFromSource(const char* vert_shader, const char* geo_shader, const char* frag_shader, + bool separable_program = false) { + OGLShader vert, geo, frag; + if (vert_shader) + vert.Create(vert_shader, GL_VERTEX_SHADER); + if (geo_shader) + geo.Create(geo_shader, GL_GEOMETRY_SHADER); + if (frag_shader) + frag.Create(frag_shader, GL_FRAGMENT_SHADER); + Create(separable_program, vert.handle, geo.handle, frag.handle); } /// Deletes the internal OpenGL resource @@ -102,7 +155,39 @@ public: if (handle == 0) return; glDeleteProgram(handle); - OpenGLState::ResetProgram(handle); + OpenGLState::GetCurState().ResetProgram(handle).Apply(); + handle = 0; + } + + GLuint handle = 0; +}; + +class OGLPipeline : private NonCopyable { +public: + OGLPipeline() = default; + OGLPipeline(OGLPipeline&& o) noexcept : handle{std::exchange<GLuint>(o.handle, 0)} {} + + ~OGLPipeline() { + Release(); + } + OGLPipeline& operator=(OGLPipeline&& o) noexcept { + handle = std::exchange<GLuint>(o.handle, 0); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create() { + if (handle != 0) + return; + glGenProgramPipelines(1, &handle); + } + + /// Deletes the internal OpenGL resource + void Release() { + if (handle == 0) + return; + glDeleteProgramPipelines(1, &handle); + OpenGLState::GetCurState().ResetPipeline(handle).Apply(); handle = 0; } @@ -112,14 +197,16 @@ public: class OGLBuffer : private NonCopyable { public: OGLBuffer() = default; - OGLBuffer(OGLBuffer&& o) { - std::swap(handle, o.handle); - } + + OGLBuffer(OGLBuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLBuffer() { Release(); } - OGLBuffer& operator=(OGLBuffer&& o) { - std::swap(handle, o.handle); + + OGLBuffer& operator=(OGLBuffer&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } @@ -135,24 +222,59 @@ public: if (handle == 0) return; glDeleteBuffers(1, &handle); - OpenGLState::ResetBuffer(handle); + OpenGLState::GetCurState().ResetBuffer(handle).Apply(); handle = 0; } GLuint handle = 0; }; +class OGLSync : private NonCopyable { +public: + OGLSync() = default; + + OGLSync(OGLSync&& o) noexcept : handle(std::exchange(o.handle, nullptr)) {} + + ~OGLSync() { + Release(); + } + OGLSync& operator=(OGLSync&& o) noexcept { + Release(); + handle = std::exchange(o.handle, nullptr); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create() { + if (handle != 0) + return; + handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + /// Deletes the internal OpenGL resource + void Release() { + if (handle == 0) + return; + glDeleteSync(handle); + handle = 0; + } + + GLsync handle = 0; +}; + class OGLVertexArray : private NonCopyable { public: OGLVertexArray() = default; - OGLVertexArray(OGLVertexArray&& o) { - std::swap(handle, o.handle); - } + + OGLVertexArray(OGLVertexArray&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLVertexArray() { Release(); } - OGLVertexArray& operator=(OGLVertexArray&& o) { - std::swap(handle, o.handle); + + OGLVertexArray& operator=(OGLVertexArray&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } @@ -168,7 +290,7 @@ public: if (handle == 0) return; glDeleteVertexArrays(1, &handle); - OpenGLState::ResetVertexArray(handle); + OpenGLState::GetCurState().ResetVertexArray(handle).Apply(); handle = 0; } @@ -178,14 +300,16 @@ public: class OGLFramebuffer : private NonCopyable { public: OGLFramebuffer() = default; - OGLFramebuffer(OGLFramebuffer&& o) { - std::swap(handle, o.handle); - } + + OGLFramebuffer(OGLFramebuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + ~OGLFramebuffer() { Release(); } - OGLFramebuffer& operator=(OGLFramebuffer&& o) { - std::swap(handle, o.handle); + + OGLFramebuffer& operator=(OGLFramebuffer&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); return *this; } @@ -201,7 +325,7 @@ public: if (handle == 0) return; glDeleteFramebuffers(1, &handle); - OpenGLState::ResetFramebuffer(handle); + OpenGLState::GetCurState().ResetFramebuffer(handle).Apply(); handle = 0; } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp new file mode 100644 index 000000000..6ec0a0742 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -0,0 +1,1722 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <map> +#include <set> +#include <string> +#include <string_view> +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/renderer_opengl/gl_shader_decompiler.h" + +namespace GLShader { +namespace Decompiler { + +using Tegra::Shader::Attribute; +using Tegra::Shader::Instruction; +using Tegra::Shader::LogicOperation; +using Tegra::Shader::OpCode; +using Tegra::Shader::Register; +using Tegra::Shader::Sampler; +using Tegra::Shader::SubOp; + +constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; + +class DecompileFail : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +/// Describes the behaviour of code path of a given entry point and a return point. +enum class ExitMethod { + Undetermined, ///< Internal value. Only occur when analyzing JMP loop. + AlwaysReturn, ///< All code paths reach the return point. + Conditional, ///< Code path reaches the return point or an END instruction conditionally. + AlwaysEnd, ///< All code paths reach a END instruction. +}; + +/// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction. +struct Subroutine { + /// Generates a name suitable for GLSL source code. + std::string GetName() const { + return "sub_" + std::to_string(begin) + '_' + std::to_string(end); + } + + u32 begin; ///< Entry point of the subroutine. + u32 end; ///< Return point of the subroutine. + ExitMethod exit_method; ///< Exit method of the subroutine. + std::set<u32> labels; ///< Addresses refereced by JMP instructions. + + bool operator<(const Subroutine& rhs) const { + return std::tie(begin, end) < std::tie(rhs.begin, rhs.end); + } +}; + +/// Analyzes shader code and produces a set of subroutines. +class ControlFlowAnalyzer { +public: + ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) + : program_code(program_code) { + + // Recursively finds all subroutines. + const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END); + if (program_main.exit_method != ExitMethod::AlwaysEnd) + throw DecompileFail("Program does not always end"); + } + + std::set<Subroutine> GetSubroutines() { + return std::move(subroutines); + } + +private: + const ProgramCode& program_code; + std::set<Subroutine> subroutines; + std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; + + /// Adds and analyzes a new subroutine if it is not added yet. + const Subroutine& AddSubroutine(u32 begin, u32 end) { + auto iter = subroutines.find(Subroutine{begin, end}); + if (iter != subroutines.end()) + return *iter; + + Subroutine subroutine{begin, end}; + subroutine.exit_method = Scan(begin, end, subroutine.labels); + if (subroutine.exit_method == ExitMethod::Undetermined) + throw DecompileFail("Recursive function detected"); + return *subroutines.insert(std::move(subroutine)).first; + } + + /// Merges exit method of two parallel branches. + static ExitMethod ParallelExit(ExitMethod a, ExitMethod b) { + if (a == ExitMethod::Undetermined) { + return b; + } + if (b == ExitMethod::Undetermined) { + return a; + } + if (a == b) { + return a; + } + return ExitMethod::Conditional; + } + + /// Scans a range of code for labels and determines the exit method. + ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) { + auto [iter, inserted] = + exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); + ExitMethod& exit_method = iter->second; + if (!inserted) + return exit_method; + + for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) { + const Instruction instr = {program_code[offset]}; + if (const auto opcode = OpCode::Decode(instr)) { + switch (opcode->GetId()) { + case OpCode::Id::EXIT: { + // The EXIT instruction can be predicated, which means that the shader can + // conditionally end on this instruction. We have to consider the case where the + // condition is not met and check the exit method of that other basic block. + using Tegra::Shader::Pred; + if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { + return exit_method = ExitMethod::AlwaysEnd; + } else { + ExitMethod not_met = Scan(offset + 1, end, labels); + return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met); + } + } + case OpCode::Id::BRA: { + u32 target = offset + instr.bra.GetBranchTarget(); + labels.insert(target); + ExitMethod no_jmp = Scan(offset + 1, end, labels); + ExitMethod jmp = Scan(target, end, labels); + return exit_method = ParallelExit(no_jmp, jmp); + } + } + } + } + return exit_method = ExitMethod::AlwaysReturn; + } +}; + +class ShaderWriter { +public: + void AddLine(std::string_view text) { + DEBUG_ASSERT(scope >= 0); + if (!text.empty()) { + AppendIndentation(); + } + shader_source += text; + AddNewLine(); + } + + void AddLine(char character) { + DEBUG_ASSERT(scope >= 0); + AppendIndentation(); + shader_source += character; + AddNewLine(); + } + + void AddNewLine() { + DEBUG_ASSERT(scope >= 0); + shader_source += '\n'; + } + + std::string GetResult() { + return std::move(shader_source); + } + + int scope = 0; + +private: + void AppendIndentation() { + shader_source.append(static_cast<size_t>(scope) * 4, ' '); + } + + std::string shader_source; +}; + +/** + * Represents an emulated shader register, used to track the state of that register for emulation + * with GLSL. At this time, a register can be used as a float or an integer. This class is used for + * bookkeeping within the GLSL program. + */ +class GLSLRegister { +public: + enum class Type { + Float, + Integer, + UnsignedInteger, + }; + + GLSLRegister(size_t index, ShaderWriter& shader) : index{index}, shader{shader} {} + + /// Gets the GLSL type string for a register + static std::string GetTypeString(Type type) { + switch (type) { + case Type::Float: + return "float"; + case Type::Integer: + return "int"; + case Type::UnsignedInteger: + return "uint"; + } + + UNREACHABLE(); + return {}; + } + + /// Gets the GLSL register prefix string, used for declarations and referencing + static std::string GetPrefixString(Type type) { + return "reg_" + GetTypeString(type) + '_'; + } + + /// Returns a GLSL string representing the current state of the register + const std::string GetActiveString() { + declr_type.insert(active_type); + return GetPrefixString(active_type) + std::to_string(index); + } + + /// Returns true if the active type is a float + bool IsFloat() const { + return active_type == Type::Float; + } + + /// Returns true if the active type is an integer + bool IsInteger() const { + return active_type == Type::Integer; + } + + /// Returns the current active type of the register + Type GetActiveType() const { + return active_type; + } + + /// Returns the index of the register + size_t GetIndex() const { + return index; + } + + /// Returns a set of the declared types of the register + const std::set<Type>& DeclaredTypes() const { + return declr_type; + } + +private: + const size_t index; + const std::string float_str; + const std::string integer_str; + ShaderWriter& shader; + Type active_type{Type::Float}; + std::set<Type> declr_type; +}; + +/** + * Used to manage shader registers that are emulated with GLSL. This class keeps track of the state + * of all registers (e.g. whether they are currently being used as Floats or Integers), and + * generates the necessary GLSL code to perform conversions as needed. This class is used for + * bookkeeping within the GLSL program. + */ +class GLSLRegisterManager { +public: + GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, + const Maxwell3D::Regs::ShaderStage& stage) + : shader{shader}, declarations{declarations}, stage{stage} { + BuildRegisterList(); + } + + /** + * Returns code that does an integer size conversion for the specified size. + * @param value Value to perform integer size conversion on. + * @param size Register size to use for conversion instructions. + * @returns GLSL string corresponding to the value converted to the specified size. + */ + static std::string ConvertIntegerSize(const std::string& value, Register::Size size) { + switch (size) { + case Register::Size::Byte: + return "((" + value + " << 24) >> 24)"; + case Register::Size::Short: + return "((" + value + " << 16) >> 16)"; + case Register::Size::Word: + // Default - do nothing + return value; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size)); + UNREACHABLE(); + } + } + + /** + * Gets a register as an float. + * @param reg The register to get. + * @param elem The element to use for the operation. + * @returns GLSL string corresponding to the register as a float. + */ + std::string GetRegisterAsFloat(const Register& reg, unsigned elem = 0) { + ASSERT(regs[reg].IsFloat()); + return GetRegister(reg, elem); + } + + /** + * Gets a register as an integer. + * @param reg The register to get. + * @param elem The element to use for the operation. + * @param is_signed Whether to get the register as a signed (or unsigned) integer. + * @param size Register size to use for conversion instructions. + * @returns GLSL string corresponding to the register as an integer. + */ + std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, bool is_signed = true, + Register::Size size = Register::Size::Word) { + const std::string func = GetGLSLConversionFunc( + GLSLRegister::Type::Float, + is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger); + + std::string value = func + '(' + GetRegister(reg, elem) + ')'; + + return ConvertIntegerSize(value, size); + } + + /** + * Writes code that does a register assignment to float value operation. + * @param reg The destination register to use. + * @param elem The element to use for the operation. + * @param value The code representing the value to assign. + * @param dest_num_components Number of components in the destination. + * @param value_num_components Number of components in the value. + * @param is_saturated Optional, when True, saturates the provided value. + * @param dest_elem Optional, the destination element to use for the operation. + */ + void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value, + u64 dest_num_components, u64 value_num_components, + bool is_saturated = false, u64 dest_elem = 0) { + + SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value, + dest_num_components, value_num_components, dest_elem); + } + + /** + * Writes code that does a register assignment to integer value operation. + * @param reg The destination register to use. + * @param elem The element to use for the operation. + * @param value The code representing the value to assign. + * @param dest_num_components Number of components in the destination. + * @param value_num_components Number of components in the value. + * @param is_saturated Optional, when True, saturates the provided value. + * @param dest_elem Optional, the destination element to use for the operation. + * @param size Register size to use for conversion instructions. + */ + void SetRegisterToInteger(const Register& reg, bool is_signed, u64 elem, + const std::string& value, u64 dest_num_components, + u64 value_num_components, bool is_saturated = false, + u64 dest_elem = 0, Register::Size size = Register::Size::Word) { + ASSERT_MSG(!is_saturated, "Unimplemented"); + + const std::string func = GetGLSLConversionFunc( + is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger, + GLSLRegister::Type::Float); + + SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')', + dest_num_components, value_num_components, dest_elem); + } + + /** + * Writes code that does a register assignment to input attribute operation. Input attributes + * are stored as floats, so this may require conversion. + * @param reg The destination register to use. + * @param elem The element to use for the operation. + * @param attribute The input attribute to use as the source value. + */ + void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute) { + std::string dest = GetRegisterAsFloat(reg); + std::string src = GetInputAttribute(attribute) + GetSwizzle(elem); + + if (regs[reg].IsFloat()) { + shader.AddLine(dest + " = " + src + ';'); + } else if (regs[reg].IsInteger()) { + shader.AddLine(dest + " = floatBitsToInt(" + src + ");"); + } else { + UNREACHABLE(); + } + } + + /** + * Writes code that does a output attribute assignment to register operation. Output attributes + * are stored as floats, so this may require conversion. + * @param attribute The destination output attribute. + * @param elem The element to use for the operation. + * @param reg The register to use as the source value. + */ + void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) { + std::string dest = GetOutputAttribute(attribute) + GetSwizzle(elem); + std::string src = GetRegisterAsFloat(reg); + ASSERT_MSG(regs[reg].IsFloat(), "Output attributes must be set to a float"); + shader.AddLine(dest + " = " + src + ';'); + } + + /// Generates code representing a uniform (C buffer) register, interpreted as the input type. + std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) { + declr_const_buffers[index].MarkAsUsed(index, offset, stage); + std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset) + ']'; + + if (type == GLSLRegister::Type::Float) { + return value; + } else if (type == GLSLRegister::Type::Integer) { + return "floatBitsToInt(" + value + ')'; + } else { + UNREACHABLE(); + } + } + + std::string GetUniformIndirect(u64 index, s64 offset, const Register& index_reg, + GLSLRegister::Type type) { + declr_const_buffers[index].MarkAsUsedIndirect(index, stage); + std::string value = 'c' + std::to_string(index) + "[(floatBitsToInt(" + + GetRegister(index_reg, 0) + ") + " + std::to_string(offset) + ") / 4]"; + + if (type == GLSLRegister::Type::Float) { + return value; + } else if (type == GLSLRegister::Type::Integer) { + return "floatBitsToInt(" + value + ')'; + } else { + UNREACHABLE(); + } + } + + /// Add declarations for registers + void GenerateDeclarations() { + for (const auto& reg : regs) { + for (const auto& type : reg.DeclaredTypes()) { + declarations.AddLine(GLSLRegister::GetTypeString(type) + ' ' + + GLSLRegister::GetPrefixString(type) + + std::to_string(reg.GetIndex()) + " = 0;"); + } + } + declarations.AddNewLine(); + + for (const auto& index : declr_input_attribute) { + // TODO(bunnei): Use proper number of elements for these + declarations.AddLine("layout(location = " + + std::to_string(static_cast<u32>(index) - + static_cast<u32>(Attribute::Index::Attribute_0)) + + ") in vec4 " + GetInputAttribute(index) + ';'); + } + declarations.AddNewLine(); + + for (const auto& index : declr_output_attribute) { + // TODO(bunnei): Use proper number of elements for these + declarations.AddLine("layout(location = " + + std::to_string(static_cast<u32>(index) - + static_cast<u32>(Attribute::Index::Attribute_0)) + + ") out vec4 " + GetOutputAttribute(index) + ';'); + } + declarations.AddNewLine(); + + unsigned const_buffer_layout = 0; + for (const auto& entry : GetConstBuffersDeclarations()) { + declarations.AddLine("layout(std430) buffer " + entry.GetName()); + declarations.AddLine('{'); + declarations.AddLine(" float c" + std::to_string(entry.GetIndex()) + "[];"); + declarations.AddLine("};"); + declarations.AddNewLine(); + ++const_buffer_layout; + } + 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(); + } + } + + /// Returns a list of constant buffer declarations + std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { + std::vector<ConstBufferEntry> result; + std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), + std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); + return result; + } + + /// Returns a list of samplers used in the shader + 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) { + size_t offset = static_cast<size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + + if (itr != used_samplers.end()) { + return itr->GetName(); + } + + // Otherwise create a new mapping for this sampler + size_t next_index = used_samplers.size(); + SamplerEntry entry{stage, offset, next_index}; + used_samplers.emplace_back(entry); + return entry.GetName(); + } + +private: + /// Build GLSL conversion function, e.g. floatBitsToInt, intBitsToFloat, etc. + const std::string GetGLSLConversionFunc(GLSLRegister::Type src, GLSLRegister::Type dest) const { + const std::string src_type = GLSLRegister::GetTypeString(src); + std::string dest_type = GLSLRegister::GetTypeString(dest); + dest_type[0] = toupper(dest_type[0]); + return src_type + "BitsTo" + dest_type; + } + + /// Generates code representing a temporary (GPR) register. + std::string GetRegister(const Register& reg, unsigned elem) { + if (reg == Register::ZeroIndex) { + return "0"; + } + + return regs[reg.GetSwizzledIndex(elem)].GetActiveString(); + } + + /** + * Writes code that does a register assignment to value operation. + * @param reg The destination register to use. + * @param elem The element to use for the operation. + * @param value The code representing the value to assign. + * @param dest_num_components Number of components in the destination. + * @param value_num_components Number of components in the value. + * @param dest_elem Optional, the destination element to use for the operation. + */ + void SetRegister(const Register& reg, u64 elem, const std::string& value, + u64 dest_num_components, u64 value_num_components, u64 dest_elem) { + std::string dest = GetRegister(reg, dest_elem); + if (dest_num_components > 1) { + dest += GetSwizzle(elem); + } + + std::string src = '(' + value + ')'; + if (value_num_components > 1) { + src += GetSwizzle(elem); + } + + shader.AddLine(dest + " = " + src + ';'); + } + + /// Build the GLSL register list. + void BuildRegisterList() { + for (size_t index = 0; index < Register::NumRegisters; ++index) { + regs.emplace_back(index, shader); + } + } + + /// Generates code representing an input attribute register. + std::string GetInputAttribute(Attribute::Index attribute) { + switch (attribute) { + case Attribute::Index::Position: + return "position"; + case Attribute::Index::TessCoordInstanceIDVertexID: + // TODO(Subv): Find out what the values are for the first two elements when inside a + // vertex shader, and what's the value of the fourth element when inside a Tess Eval + // shader. + ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); + return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))"; + default: + const u32 index{static_cast<u32>(attribute) - + static_cast<u32>(Attribute::Index::Attribute_0)}; + if (attribute >= Attribute::Index::Attribute_0) { + declr_input_attribute.insert(attribute); + return "input_attribute_" + std::to_string(index); + } + + NGLOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", index); + UNREACHABLE(); + } + } + + /// Generates code representing an output attribute register. + std::string GetOutputAttribute(Attribute::Index attribute) { + switch (attribute) { + case Attribute::Index::Position: + return "position"; + default: + const u32 index{static_cast<u32>(attribute) - + static_cast<u32>(Attribute::Index::Attribute_0)}; + if (attribute >= Attribute::Index::Attribute_0) { + declr_output_attribute.insert(attribute); + return "output_attribute_" + std::to_string(index); + } + + NGLOG_CRITICAL(HW_GPU, "Unhandled output attribute: {}", index); + UNREACHABLE(); + } + } + + /// Generates code to use for a swizzle operation. + static std::string GetSwizzle(u64 elem) { + ASSERT(elem <= 3); + std::string swizzle = "."; + swizzle += "xyzw"[elem]; + return swizzle; + } + + ShaderWriter& shader; + ShaderWriter& declarations; + std::vector<GLSLRegister> regs; + std::set<Attribute::Index> declr_input_attribute; + std::set<Attribute::Index> declr_output_attribute; + std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers; + std::vector<SamplerEntry> used_samplers; + const Maxwell3D::Regs::ShaderStage& stage; +}; + +class GLSLGenerator { +public: + GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code, + u32 main_offset, Maxwell3D::Regs::ShaderStage stage) + : subroutines(subroutines), program_code(program_code), main_offset(main_offset), + stage(stage) { + + Generate(); + } + + std::string GetShaderCode() { + return declarations.GetResult() + shader.GetResult(); + } + + /// Returns entries in the shader that are useful for external functions + ShaderEntries GetEntries() const { + return {regs.GetConstBuffersDeclarations(), regs.GetSamplers()}; + } + +private: + /// Gets the Subroutine object corresponding to the specified address. + const Subroutine& GetSubroutine(u32 begin, u32 end) const { + auto iter = subroutines.find(Subroutine{begin, end}); + ASSERT(iter != subroutines.end()); + return *iter; + } + + /// Generates code representing a 19-bit immediate value + static std::string GetImmediate19(const Instruction& instr) { + return std::to_string(instr.alu.GetImm20_19()); + } + + /// Generates code representing a 32-bit immediate value + static std::string GetImmediate32(const Instruction& instr) { + return std::to_string(instr.alu.GetImm20_32()); + } + + /// Generates code representing a texture sampler. + std::string GetSampler(const Sampler& sampler) { + return regs.AccessSampler(sampler); + } + + /** + * Adds code that calls a subroutine. + * @param subroutine the subroutine to call. + */ + void CallSubroutine(const Subroutine& subroutine) { + if (subroutine.exit_method == ExitMethod::AlwaysEnd) { + shader.AddLine(subroutine.GetName() + "();"); + shader.AddLine("return true;"); + } else if (subroutine.exit_method == ExitMethod::Conditional) { + shader.AddLine("if (" + subroutine.GetName() + "()) { return true; }"); + } else { + shader.AddLine(subroutine.GetName() + "();"); + } + } + + /* + * Writes code that assigns a predicate boolean variable. + * @param pred The id of the predicate to write to. + * @param value The expression value to assign to the predicate. + */ + void SetPredicate(u64 pred, const std::string& value) { + using Tegra::Shader::Pred; + // Can't assign to the constant predicate. + ASSERT(pred != static_cast<u64>(Pred::UnusedIndex)); + + std::string variable = 'p' + std::to_string(pred); + shader.AddLine(variable + " = " + value + ';'); + declr_predicates.insert(std::move(variable)); + } + + /* + * Returns the condition to use in the 'if' for a predicated instruction. + * @param instr Instruction to generate the if condition for. + * @returns string containing the predicate condition. + */ + std::string GetPredicateCondition(u64 index, bool negate) const { + using Tegra::Shader::Pred; + std::string variable; + + // Index 7 is used as an 'Always True' condition. + if (index == static_cast<u64>(Pred::UnusedIndex)) + variable = "true"; + else + variable = 'p' + std::to_string(index); + + if (negate) { + return "!(" + variable + ')'; + } + + return variable; + } + + /** + * Returns the comparison string to use to compare two values in the 'set' family of + * instructions. + * @params condition The condition used in the 'set'-family instruction. + * @returns String corresponding to the GLSL operator that matches the desired comparison. + */ + std::string GetPredicateComparison(Tegra::Shader::PredCondition condition) const { + using Tegra::Shader::PredCondition; + static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = { + {PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="}, + {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"}, + {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="}, + }; + + auto comparison = PredicateComparisonStrings.find(condition); + ASSERT_MSG(comparison != PredicateComparisonStrings.end(), + "Unknown predicate comparison operation"); + return comparison->second; + } + + /** + * Returns the operator string to use to combine two predicates in the 'setp' family of + * instructions. + * @params operation The operator used in the 'setp'-family instruction. + * @returns String corresponding to the GLSL operator that matches the desired operator. + */ + std::string GetPredicateCombiner(Tegra::Shader::PredOperation operation) const { + using Tegra::Shader::PredOperation; + static const std::unordered_map<PredOperation, const char*> PredicateOperationStrings = { + {PredOperation::And, "&&"}, + {PredOperation::Or, "||"}, + {PredOperation::Xor, "^^"}, + }; + + auto op = PredicateOperationStrings.find(operation); + ASSERT_MSG(op != PredicateOperationStrings.end(), "Unknown predicate operation"); + return op->second; + } + + /* + * Returns whether the instruction at the specified offset is a 'sched' instruction. + * Sched instructions always appear before a sequence of 3 instructions. + */ + bool IsSchedInstruction(u32 offset) const { + // sched instructions appear once every 4 instructions. + static constexpr size_t SchedPeriod = 4; + u32 absolute_offset = offset - main_offset; + + return (absolute_offset % SchedPeriod) == 0; + } + + void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a, + const std::string& op_b) { + switch (logic_op) { + case LogicOperation::And: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Or: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Xor: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1); + break; + } + case LogicOperation::PassB: { + regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1); + break; + } + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); + UNREACHABLE(); + } + } + + /** + * Compiles a single instruction from Tegra to GLSL. + * @param offset the offset of the Tegra shader instruction. + * @return the offset of the next instruction to execute. Usually it is the current offset + * + 1. If the current instruction always terminates the program, returns PROGRAM_END. + */ + u32 CompileInstr(u32 offset) { + // Ignore sched instructions when generating code. + if (IsSchedInstruction(offset)) { + return offset + 1; + } + + const Instruction instr = {program_code[offset]}; + const auto opcode = OpCode::Decode(instr); + + // Decoding failure + if (!opcode) { + NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {0:x}", instr.value); + UNREACHABLE(); + } + + shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName()); + + using Tegra::Shader::Pred; + ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, + "NeverExecute predicate not implemented"); + + if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) { + shader.AddLine("if (" + + GetPredicateCondition(instr.pred.pred_index, instr.negate_pred != 0) + + ')'); + shader.AddLine('{'); + ++shader.scope; + } + + switch (opcode->GetType()) { + case OpCode::Type::Arithmetic: { + std::string op_a = instr.alu.negate_a ? "-" : ""; + op_a += regs.GetRegisterAsFloat(instr.gpr8); + if (instr.alu.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + std::string op_b = instr.alu.negate_b ? "-" : ""; + + if (instr.is_b_imm) { + op_b += GetImmediate19(instr); + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsFloat(instr.gpr20); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); + } + } + + if (instr.alu.abs_b) { + op_b = "abs(" + op_b + ')'; + } + + switch (opcode->GetId()) { + case OpCode::Id::MOV_C: + case OpCode::Id::MOV_R: { + regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); + break; + } + + case OpCode::Id::MOV32_IMM: { + // mov32i doesn't have abs or neg bits. + regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1); + break; + } + case OpCode::Id::FMUL_C: + case OpCode::Id::FMUL_R: + case OpCode::Id::FMUL_IMM: { + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, + instr.alu.saturate_d); + break; + } + case OpCode::Id::FMUL32_IMM: { + // fmul32i doesn't have abs or neg bits. + regs.SetRegisterToFloat( + instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + break; + } + case OpCode::Id::FADD_C: + case OpCode::Id::FADD_R: + case OpCode::Id::FADD_IMM: { + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, + instr.alu.saturate_d); + break; + } + case OpCode::Id::MUFU: { + switch (instr.sub_op) { + case SubOp::Cos: + regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Sin: + regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Ex2: + regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Lg2: + regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Rcp: + regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Rsq: + regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1, + instr.alu.saturate_d); + break; + case SubOp::Min: + regs.SetRegisterToFloat(instr.gpr0, 0, "min(" + op_a + "," + op_b + ')', 1, 1, + instr.alu.saturate_d); + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}", + static_cast<unsigned>(instr.sub_op.Value())); + UNREACHABLE(); + } + break; + } + case OpCode::Id::FMNMX_C: + case OpCode::Id::FMNMX_R: + case OpCode::Id::FMNMX_IMM: { + std::string condition = + GetPredicateCondition(instr.alu.fmnmx.pred, instr.alu.fmnmx.negate_pred != 0); + std::string parameters = op_a + ',' + op_b; + regs.SetRegisterToFloat(instr.gpr0, 0, + '(' + condition + ") ? min(" + parameters + ") : max(" + + parameters + ')', + 1, 1); + break; + } + case OpCode::Id::RRO_C: + 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. + regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); + NGLOG_WARNING(HW_GPU, "RRO instruction is incomplete"); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + case OpCode::Type::Bfe: { + ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented"); + + std::string op_a = instr.bfe.negate_a ? "-" : ""; + op_a += regs.GetRegisterAsInteger(instr.gpr8); + + switch (opcode->GetId()) { + case OpCode::Id::BFE_IMM: { + std::string inner_shift = + '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')'; + std::string outer_shift = + '(' + inner_shift + " >> " + + std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + + break; + } + + case OpCode::Type::Shift: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); + std::string op_b; + + if (instr.is_b_imm) { + op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsInteger(instr.gpr20); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } + } + + switch (opcode->GetId()) { + case OpCode::Id::SHR_C: + case OpCode::Id::SHR_R: + case OpCode::Id::SHR_IMM: { + if (!instr.shift.is_signed) { + // Logical shift right + op_a = "uint(" + op_a + ')'; + } + + // Cast to int is superfluous for arithmetic shift, it's only for a logical shift + regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')', + 1, 1); + break; + } + case OpCode::Id::SHL_C: + case OpCode::Id::SHL_R: + case OpCode::Id::SHL_IMM: + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1); + break; + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + + case OpCode::Type::ArithmeticIntegerImmediate: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); + std::string op_b = std::to_string(instr.alu.imm20_32.Value()); + + switch (opcode->GetId()) { + case OpCode::Id::IADD32I: + if (instr.iadd32i.negate_a) + op_a = "-(" + op_a + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, + instr.iadd32i.saturate != 0); + break; + case OpCode::Id::LOP32I: { + if (instr.alu.lop32i.invert_a) + op_a = "~(" + op_a + ')'; + + if (instr.alu.lop32i.invert_b) + op_b = "~(" + op_b + ')'; + + WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}", + opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + case OpCode::Type::ArithmeticInteger: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); + std::string op_b; + if (instr.is_b_imm) { + op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsInteger(instr.gpr20); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } + } + + switch (opcode->GetId()) { + case OpCode::Id::IADD_C: + case OpCode::Id::IADD_R: + case OpCode::Id::IADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, + instr.alu.saturate_d); + break; + } + case OpCode::Id::ISCADD_C: + case OpCode::Id::ISCADD_R: + case OpCode::Id::ISCADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + + std::string shift = std::to_string(instr.alu_integer.shift_amount.Value()); + + regs.SetRegisterToInteger(instr.gpr0, true, 0, + "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); + break; + } + 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); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", + opcode->GetName()); + UNREACHABLE(); + } + } + + break; + } + case OpCode::Type::Ffma: { + std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + std::string op_b = instr.ffma.negate_b ? "-" : ""; + std::string op_c = instr.ffma.negate_c ? "-" : ""; + + switch (opcode->GetId()) { + case OpCode::Id::FFMA_CR: { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); + op_c += regs.GetRegisterAsFloat(instr.gpr39); + break; + } + case OpCode::Id::FFMA_RR: { + op_b += regs.GetRegisterAsFloat(instr.gpr20); + op_c += regs.GetRegisterAsFloat(instr.gpr39); + break; + } + case OpCode::Id::FFMA_RC: { + op_b += regs.GetRegisterAsFloat(instr.gpr39); + op_c += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); + break; + } + case OpCode::Id::FFMA_IMM: { + op_b += GetImmediate19(instr); + op_c += regs.GetRegisterAsFloat(instr.gpr39); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1, + instr.alu.saturate_d); + 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"); + + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); + + if (instr.conversion.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, + 1, instr.alu.saturate_d, 0, instr.conversion.dest_size); + break; + } + case OpCode::Id::I2F_R: { + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); + ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); + + if (instr.conversion.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + break; + } + case OpCode::Id::F2F_R: { + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); + + switch (instr.conversion.f2f.rounding) { + case Tegra::Shader::F2fRoundingOp::None: + break; + case Tegra::Shader::F2fRoundingOp::Floor: + op_a = "floor(" + op_a + ')'; + break; + case Tegra::Shader::F2fRoundingOp::Ceil: + op_a = "ceil(" + op_a + ')'; + break; + case Tegra::Shader::F2fRoundingOp::Trunc: + op_a = "trunc(" + op_a + ')'; + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented f2f rounding mode {}", + static_cast<u32>(instr.conversion.f2f.rounding.Value())); + UNREACHABLE(); + break; + } + + if (instr.conversion.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d); + break; + } + case OpCode::Id::F2I_R: { + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); + + if (instr.conversion.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + switch (instr.conversion.f2i.rounding) { + case Tegra::Shader::F2iRoundingOp::None: + break; + case Tegra::Shader::F2iRoundingOp::Floor: + op_a = "floor(" + op_a + ')'; + break; + case Tegra::Shader::F2iRoundingOp::Ceil: + op_a = "ceil(" + op_a + ')'; + break; + case Tegra::Shader::F2iRoundingOp::Trunc: + op_a = "trunc(" + op_a + ')'; + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented f2i rounding mode {}", + static_cast<u32>(instr.conversion.f2i.rounding.Value())); + UNREACHABLE(); + break; + } + + if (instr.conversion.is_output_signed) { + op_a = "int(" + op_a + ')'; + } else { + op_a = "uint(" + op_a + ')'; + } + + regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, + 1, false, 0, instr.conversion.dest_size); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + case OpCode::Type::Memory: { + switch (opcode->GetId()) { + case OpCode::Id::LD_A: { + ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); + regs.SetRegisterToInputAttibute(instr.gpr0, instr.attribute.fmt20.element, + instr.attribute.fmt20.index); + break; + } + case OpCode::Id::LD_C: { + ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented"); + + std::string op_a = + regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, instr.gpr8, + GLSLRegister::Type::Float); + std::string op_b = + regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, instr.gpr8, + GLSLRegister::Type::Float); + + switch (instr.ld_c.type.Value()) { + case Tegra::Shader::UniformType::Single: + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + break; + + case Tegra::Shader::UniformType::Double: + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + regs.SetRegisterToFloat(instr.gpr0.Value() + 1, 0, op_b, 1, 1); + break; + + default: + NGLOG_CRITICAL(HW_GPU, "Unhandled type: {}", + static_cast<unsigned>(instr.ld_c.type.Value())); + UNREACHABLE(); + } + break; + } + case OpCode::Id::ST_A: { + ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); + regs.SetOutputAttributeToRegister(instr.attribute.fmt20.index, + instr.attribute.fmt20.element, instr.gpr0); + 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 + ");"; + // 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 = "texture(" + sampler + ", coords)"; + + 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::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 + ");"; + // 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 = "texture(" + sampler + ", coords)"; + + // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA + // goes into gpr28+0 and gpr28+1 + size_t offset{}; + + for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) { + for (unsigned elem = 0; elem < 2; ++elem) { + if (!instr.texs.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + regs.SetRegisterToFloat(dest, elem + offset, texture, 1, 4, false, elem); + } + + if (!instr.texs.HasTwoDestinations()) { + // Skip the second destination + break; + } + + offset += 2; + } + --shader.scope; + shader.AddLine("}"); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + case OpCode::Type::FloatSetPredicate: { + std::string op_a = instr.fsetp.neg_a ? "-" : ""; + op_a += regs.GetRegisterAsFloat(instr.gpr8); + + if (instr.fsetp.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + std::string op_b{}; + + if (instr.is_b_imm) { + if (instr.fsetp.neg_b) { + // Only the immediate version of fsetp has a neg_b bit. + op_b += '-'; + } + op_b += '(' + GetImmediate19(instr) + ')'; + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsFloat(instr.gpr20); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); + } + } + + if (instr.fsetp.abs_b) { + op_b = "abs(" + op_b + ')'; + } + + using Tegra::Shader::Pred; + // We can't use the constant predicate as destination. + ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + std::string second_pred = + GetPredicateCondition(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); + + std::string comparator = GetPredicateComparison(instr.fsetp.cond); + std::string combiner = GetPredicateCombiner(instr.fsetp.op); + + std::string predicate = '(' + op_a + ") " + comparator + " (" + op_b + ')'; + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.fsetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.fsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.fsetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } + case OpCode::Type::IntegerSetPredicate: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed); + std::string op_b; + + if (instr.is_b_imm) { + op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.isetp.is_signed); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } + } + + using Tegra::Shader::Pred; + // We can't use the constant predicate as destination. + ASSERT(instr.isetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + std::string second_pred = + GetPredicateCondition(instr.isetp.pred39, instr.isetp.neg_pred != 0); + + std::string comparator = GetPredicateComparison(instr.isetp.cond); + std::string combiner = GetPredicateCombiner(instr.isetp.op); + + std::string predicate = '(' + op_a + ") " + comparator + " (" + op_b + ')'; + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.isetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.isetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.isetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } + case OpCode::Type::FloatSet: { + std::string op_a = instr.fset.neg_a ? "-" : ""; + op_a += regs.GetRegisterAsFloat(instr.gpr8); + + if (instr.fset.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + std::string op_b = instr.fset.neg_b ? "-" : ""; + + if (instr.is_b_imm) { + std::string imm = GetImmediate19(instr); + if (instr.fset.neg_imm) + op_b += "(-" + imm + ')'; + else + op_b += imm; + } else { + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsFloat(instr.gpr20); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); + } + } + + if (instr.fset.abs_b) { + op_b = "abs(" + op_b + ')'; + } + + // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the + // condition is true, and to 0 otherwise. + std::string second_pred = + GetPredicateCondition(instr.fset.pred39, instr.fset.neg_pred != 0); + + std::string comparator = GetPredicateComparison(instr.fset.cond); + std::string combiner = GetPredicateCombiner(instr.fset.op); + + std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " + + combiner + " (" + second_pred + "))"; + + if (instr.fset.bf) { + regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1); + } else { + regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1, + 1); + } + break; + } + case OpCode::Type::IntegerSet: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed); + + std::string op_b; + + if (instr.is_b_imm) { + op_b = std::to_string(instr.alu.GetSignedImm20_20()); + } else { + if (instr.is_b_gpr) { + op_b = regs.GetRegisterAsInteger(instr.gpr20, 0, instr.iset.is_signed); + } else { + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } + } + + // The iset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the + // condition is true, and to 0 otherwise. + std::string second_pred = + GetPredicateCondition(instr.iset.pred39, instr.iset.neg_pred != 0); + + std::string comparator = GetPredicateComparison(instr.iset.cond); + std::string combiner = GetPredicateCombiner(instr.iset.op); + + std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " + + combiner + " (" + second_pred + "))"; + + if (instr.iset.bf) { + regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1); + } else { + regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1, + 1); + } + break; + } + default: { + switch (opcode->GetId()) { + case OpCode::Id::EXIT: { + // Final color output is currently hardcoded to GPR0-3 for fragment shaders + if (stage == Maxwell3D::Regs::ShaderStage::Fragment) { + shader.AddLine("color.r = " + regs.GetRegisterAsFloat(0) + ';'); + shader.AddLine("color.g = " + regs.GetRegisterAsFloat(1) + ';'); + shader.AddLine("color.b = " + regs.GetRegisterAsFloat(2) + ';'); + shader.AddLine("color.a = " + regs.GetRegisterAsFloat(3) + ';'); + } + + shader.AddLine("return true;"); + if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { + // If this is an unconditional exit then just end processing here, otherwise + // we have to account for the possibility of the condition not being met, so + // continue processing the next instruction. + offset = PROGRAM_END - 1; + } + break; + } + case OpCode::Id::KIL: { + shader.AddLine("discard;"); + break; + } + case OpCode::Id::BRA: { + ASSERT_MSG(instr.bra.constant_buffer == 0, + "BRA with constant buffers are not implemented"); + u32 target = offset + instr.bra.GetBranchTarget(); + shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); + break; + } + case OpCode::Id::IPA: { + const auto& attribute = instr.attribute.fmt28; + regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index); + break; + } + case OpCode::Id::SSY: { + // The SSY opcode tells the GPU where to re-converge divergent execution paths, we + // can ignore this when generating GLSL code. + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + + break; + } + } + + // Close the predicate condition scope. + if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) { + --shader.scope; + shader.AddLine('}'); + } + + return offset + 1; + } + + /** + * Compiles a range of instructions from Tegra to GLSL. + * @param begin the offset of the starting instruction. + * @param end the offset where the compilation should stop (exclusive). + * @return the offset of the next instruction to compile. PROGRAM_END if the program + * terminates. + */ + u32 CompileRange(u32 begin, u32 end) { + u32 program_counter; + for (program_counter = begin; program_counter < (begin > end ? PROGRAM_END : end);) { + program_counter = CompileInstr(program_counter); + } + return program_counter; + } + + void Generate() { + // Add declarations for all subroutines + for (const auto& subroutine : subroutines) { + shader.AddLine("bool " + subroutine.GetName() + "();"); + } + shader.AddNewLine(); + + // Add the main entry point + shader.AddLine("bool exec_shader() {"); + ++shader.scope; + CallSubroutine(GetSubroutine(main_offset, PROGRAM_END)); + --shader.scope; + shader.AddLine("}\n"); + + // Add definitions for all subroutines + for (const auto& subroutine : subroutines) { + std::set<u32> labels = subroutine.labels; + + shader.AddLine("bool " + subroutine.GetName() + "() {"); + ++shader.scope; + + if (labels.empty()) { + if (CompileRange(subroutine.begin, subroutine.end) != PROGRAM_END) { + shader.AddLine("return false;"); + } + } else { + labels.insert(subroutine.begin); + shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); + shader.AddLine("while (true) {"); + ++shader.scope; + + shader.AddLine("switch (jmp_to) {"); + + for (auto label : labels) { + shader.AddLine("case " + std::to_string(label) + "u: {"); + ++shader.scope; + + auto next_it = labels.lower_bound(label + 1); + u32 next_label = next_it == labels.end() ? subroutine.end : *next_it; + + u32 compile_end = CompileRange(label, next_label); + if (compile_end > next_label && compile_end != PROGRAM_END) { + // This happens only when there is a label inside a IF/LOOP block + shader.AddLine("{ jmp_to = " + std::to_string(compile_end) + "u; break; }"); + labels.emplace(compile_end); + } + + --shader.scope; + shader.AddLine('}'); + } + + shader.AddLine("default: return false;"); + shader.AddLine('}'); + + --shader.scope; + shader.AddLine('}'); + + shader.AddLine("return false;"); + } + + --shader.scope; + shader.AddLine("}\n"); + + DEBUG_ASSERT(shader.scope == 0); + } + + GenerateDeclarations(); + } + + /// Add declarations for registers + void GenerateDeclarations() { + regs.GenerateDeclarations(); + + for (const auto& pred : declr_predicates) { + declarations.AddLine("bool " + pred + " = false;"); + } + declarations.AddNewLine(); + } + +private: + const std::set<Subroutine>& subroutines; + const ProgramCode& program_code; + const u32 main_offset; + Maxwell3D::Regs::ShaderStage stage; + + ShaderWriter shader; + ShaderWriter declarations; + GLSLRegisterManager regs{shader, declarations, stage}; + + // Declarations + std::set<std::string> declr_predicates; +}; // namespace Decompiler + +std::string GetCommonDeclarations() { + return "bool exec_shader();"; +} + +boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset, + Maxwell3D::Regs::ShaderStage stage) { + try { + auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); + GLSLGenerator generator(subroutines, program_code, main_offset, stage); + return ProgramResult{generator.GetShaderCode(), generator.GetEntries()}; + } catch (const DecompileFail& exception) { + NGLOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what()); + } + return boost::none; +} + +} // namespace Decompiler +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h new file mode 100644 index 000000000..382c76b7a --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -0,0 +1,26 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <string> +#include <boost/optional.hpp> +#include "common/common_types.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" + +namespace GLShader { +namespace Decompiler { + +using Tegra::Engines::Maxwell3D; + +std::string GetCommonDeclarations(); + +boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset, + Maxwell3D::Regs::ShaderStage stage); + +} // namespace Decompiler +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp new file mode 100644 index 000000000..c1e6fac9f --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -0,0 +1,78 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_shader_decompiler.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" + +namespace GLShader { + +using Tegra::Engines::Maxwell3D; + +static constexpr u32 PROGRAM_OFFSET{10}; + +ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConfig& config) { + std::string out = "#version 430 core\n"; + out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + out += Decompiler::GetCommonDeclarations(); + + ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Vertex) + .get_value_or({}); + out += R"( + +out gl_PerVertex { + vec4 gl_Position; +}; + +out vec4 position; + +layout (std140) uniform vs_config { + vec4 viewport_flip; +}; + +void main() { + exec_shader(); + + // Viewport can be flipped, which is unsupported by glViewport + position.xy *= viewport_flip.xy; + gl_Position = position; + + // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0 + // For now, this is here to bring order in lieu of proper emulation + position.w = 1.0; +} +)"; + out += program.first; + return {out, program.second}; +} + +ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config) { + std::string out = "#version 430 core\n"; + out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + out += Decompiler::GetCommonDeclarations(); + + ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Fragment) + .get_value_or({}); + out += R"( + +in vec4 position; +out vec4 color; + +layout (std140) uniform fs_config { + vec4 viewport_flip; +}; + +void main() { + exec_shader(); +} + +)"; + out += program.first; + return {out, program.second}; +} + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h new file mode 100644 index 000000000..ed890e0f9 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -0,0 +1,185 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "common/hash.h" + +namespace GLShader { + +constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000}; + +using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>; + +class ConstBufferEntry { + using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +public: + void MarkAsUsed(u64 index, u64 offset, Maxwell::ShaderStage stage) { + is_used = true; + this->index = static_cast<unsigned>(index); + this->stage = stage; + max_offset = std::max(max_offset, static_cast<unsigned>(offset)); + } + + void MarkAsUsedIndirect(u64 index, Maxwell::ShaderStage stage) { + is_used = true; + is_indirect = true; + this->index = static_cast<unsigned>(index); + this->stage = stage; + } + + bool IsUsed() const { + return is_used; + } + + bool IsIndirect() const { + return is_indirect; + } + + unsigned GetIndex() const { + return index; + } + + unsigned GetSize() const { + return max_offset + 1; + } + + std::string GetName() const { + return BufferBaseNames[static_cast<size_t>(stage)] + std::to_string(index); + } + +private: + static constexpr std::array<const char*, Maxwell::MaxShaderStage> BufferBaseNames = { + "buffer_vs_c", "buffer_tessc_c", "buffer_tesse_c", "buffer_gs_c", "buffer_fs_c", + }; + + bool is_used{}; + bool is_indirect{}; + unsigned index{}; + unsigned max_offset{}; + Maxwell::ShaderStage stage; +}; + +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) {} + + size_t GetOffset() const { + return offset; + } + + size_t GetIndex() const { + return sampler_index; + } + + Maxwell::ShaderStage GetStage() const { + return stage; + } + + std::string GetName() const { + return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' + + std::to_string(sampler_index) + ']'; + } + + static std::string GetArrayName(Maxwell::ShaderStage stage) { + return TextureSamplerNames[static_cast<size_t>(stage)]; + } + +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. +}; + +struct ShaderEntries { + std::vector<ConstBufferEntry> const_buffer_entries; + std::vector<SamplerEntry> texture_samplers; +}; + +using ProgramResult = std::pair<std::string, ShaderEntries>; + +struct ShaderSetup { + ShaderSetup(ProgramCode&& program_code) : program_code(std::move(program_code)) {} + + ProgramCode program_code; + bool program_code_hash_dirty = true; + + u64 GetProgramCodeHash() { + if (program_code_hash_dirty) { + program_code_hash = Common::ComputeHash64(&program_code, sizeof(program_code)); + program_code_hash_dirty = false; + } + return program_code_hash; + } + +private: + u64 program_code_hash{}; +}; + +struct MaxwellShaderConfigCommon { + void Init(ShaderSetup& setup) { + program_hash = setup.GetProgramCodeHash(); + } + + u64 program_hash; +}; + +struct MaxwellVSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> { + explicit MaxwellVSConfig(ShaderSetup& setup) { + state.Init(setup); + } +}; + +struct MaxwellFSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> { + explicit MaxwellFSConfig(ShaderSetup& setup) { + state.Init(setup); + } +}; + +/** + * Generates the GLSL vertex shader program source code for the given VS program + * @returns String of the shader source code + */ +ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConfig& config); + +/** + * Generates the GLSL fragment shader program source code for the given FS program + * @returns String of the shader source code + */ +ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config); + +} // namespace GLShader + +namespace std { + +template <> +struct hash<GLShader::MaxwellVSConfig> { + size_t operator()(const GLShader::MaxwellVSConfig& k) const { + return k.Hash(); + } +}; + +template <> +struct hash<GLShader::MaxwellFSConfig> { + size_t operator()(const GLShader::MaxwellFSConfig& k) const { + return k.Hash(); + } +}; + +} // namespace std diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp new file mode 100644 index 000000000..7c00beb33 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/hle/kernel/process.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" + +namespace GLShader { + +namespace Impl { +void SetShaderUniformBlockBinding(GLuint shader, const char* name, + Maxwell3D::Regs::ShaderStage binding, size_t expected_size) { + GLuint ub_index = glGetUniformBlockIndex(shader, name); + if (ub_index != GL_INVALID_INDEX) { + GLint ub_size = 0; + glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); + ASSERT_MSG(ub_size == expected_size, + "Uniform block size did not match! Got {}, expected {}", + static_cast<int>(ub_size), expected_size); + glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding)); + } +} + +void SetShaderUniformBlockBindings(GLuint shader) { + SetShaderUniformBlockBinding(shader, "vs_config", Maxwell3D::Regs::ShaderStage::Vertex, + sizeof(MaxwellUniformData)); + SetShaderUniformBlockBinding(shader, "gs_config", Maxwell3D::Regs::ShaderStage::Geometry, + sizeof(MaxwellUniformData)); + SetShaderUniformBlockBinding(shader, "fs_config", Maxwell3D::Regs::ShaderStage::Fragment, + sizeof(MaxwellUniformData)); +} + +} // namespace Impl + +void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + // TODO(bunnei): Support more than one viewport + viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0 : 1.0; + viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0 : 1.0; +} + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h new file mode 100644 index 000000000..4295c20a6 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -0,0 +1,174 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <tuple> +#include <unordered_map> +#include <boost/functional/hash.hpp> +#include <glad/glad.h> +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/maxwell_to_gl.h" + +namespace GLShader { + +/// Number of OpenGL texture samplers that can be used in the fragment shader +static constexpr size_t NumTextureSamplers = 32; + +using Tegra::Engines::Maxwell3D; + +namespace Impl { +void SetShaderUniformBlockBindings(GLuint shader); +void SetShaderSamplerBindings(GLuint shader); +} // namespace Impl + +/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned +// NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at +// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not. +// Not following that rule will cause problems on some AMD drivers. +struct MaxwellUniformData { + void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage); + alignas(16) GLvec4 viewport_flip; +}; +static_assert(sizeof(MaxwellUniformData) == 16, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) < 16384, + "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); + +class OGLShaderStage { +public: + OGLShaderStage() = default; + + void Create(const ProgramResult& program_result, GLenum type) { + OGLShader shader; + shader.Create(program_result.first.c_str(), type); + program.Create(true, shader.handle); + Impl::SetShaderUniformBlockBindings(program.handle); + entries = program_result.second; + } + GLuint GetHandle() const { + return program.handle; + } + + ShaderEntries GetEntries() const { + return entries; + } + +private: + OGLProgram program; + ShaderEntries entries; +}; + +// TODO(wwylele): beautify this doc +// This is a shader cache designed for translating PICA shader to GLSL shader. +// The double cache is needed because diffent KeyConfigType, which includes a hash of the code +// region (including its leftover unused code) can generate the same GLSL code. +template <typename KeyConfigType, + ProgramResult (*CodeGenerator)(const ShaderSetup&, const KeyConfigType&), + GLenum ShaderType> +class ShaderCache { +public: + ShaderCache() = default; + + using Result = std::pair<GLuint, ShaderEntries>; + + Result Get(const KeyConfigType& key, const ShaderSetup& setup) { + auto map_it = shader_map.find(key); + if (map_it == shader_map.end()) { + ProgramResult program = CodeGenerator(setup, key); + + auto [iter, new_shader] = shader_cache.emplace(program.first, OGLShaderStage{}); + OGLShaderStage& cached_shader = iter->second; + if (new_shader) { + cached_shader.Create(program, ShaderType); + } + shader_map[key] = &cached_shader; + return {cached_shader.GetHandle(), program.second}; + } else { + return {map_it->second->GetHandle(), map_it->second->GetEntries()}; + } + } + +private: + std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map; + std::unordered_map<std::string, OGLShaderStage> shader_cache; +}; + +using VertexShaders = ShaderCache<MaxwellVSConfig, &GenerateVertexShader, GL_VERTEX_SHADER>; + +using FragmentShaders = ShaderCache<MaxwellFSConfig, &GenerateFragmentShader, GL_FRAGMENT_SHADER>; + +class ProgramManager { +public: + ProgramManager() { + pipeline.Create(); + } + + ShaderEntries UseProgrammableVertexShader(const MaxwellVSConfig& config, + const ShaderSetup setup) { + ShaderEntries result; + std::tie(current.vs, result) = vertex_shaders.Get(config, setup); + return result; + } + + ShaderEntries UseProgrammableFragmentShader(const MaxwellFSConfig& config, + const ShaderSetup setup) { + ShaderEntries result; + std::tie(current.fs, result) = fragment_shaders.Get(config, setup); + return result; + } + + GLuint GetCurrentProgramStage(Maxwell3D::Regs::ShaderStage stage) { + switch (stage) { + case Maxwell3D::Regs::ShaderStage::Vertex: + return current.vs; + case Maxwell3D::Regs::ShaderStage::Fragment: + return current.fs; + } + + UNREACHABLE(); + } + + void UseTrivialGeometryShader() { + current.gs = 0; + } + + void ApplyTo(OpenGLState& state) { + // Workaround for AMD bug + glUseProgramStages(pipeline.handle, + GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, + 0); + + glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current.vs); + glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current.gs); + glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current.fs); + state.draw.shader_program = 0; + state.draw.program_pipeline = pipeline.handle; + } + +private: + struct ShaderTuple { + GLuint vs = 0, gs = 0, fs = 0; + bool operator==(const ShaderTuple& rhs) const { + return std::tie(vs, gs, fs) == std::tie(rhs.vs, rhs.gs, rhs.fs); + } + struct Hash { + std::size_t operator()(const ShaderTuple& tuple) const { + std::size_t hash = 0; + boost::hash_combine(hash, tuple.vs); + boost::hash_combine(hash, tuple.gs); + boost::hash_combine(hash, tuple.fs); + return hash; + } + }; + }; + ShaderTuple current; + VertexShaders vertex_shaders; + FragmentShaders fragment_shaders; + + std::unordered_map<ShaderTuple, OGLProgram, ShaderTuple::Hash> program_cache; + OGLPipeline pipeline; +}; + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 4da241d83..8568fface 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -10,90 +10,41 @@ namespace GLShader { -GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) { - - // Create the shaders - GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); - GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); - - GLint result = GL_FALSE; - int info_log_length; - - // Compile Vertex Shader - LOG_DEBUG(Render_OpenGL, "Compiling vertex shader..."); - - glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr); - glCompileShader(vertex_shader_id); - - // Check Vertex Shader - glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result); - glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); - - if (info_log_length > 1) { - std::vector<char> vertex_shader_error(info_log_length); - glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]); - if (result == GL_TRUE) { - LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]); - } else { - LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s", &vertex_shader_error[0]); - } - } - - // Compile Fragment Shader - LOG_DEBUG(Render_OpenGL, "Compiling fragment shader..."); - - glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr); - glCompileShader(fragment_shader_id); - - // Check Fragment Shader - glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result); - glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); - - if (info_log_length > 1) { - std::vector<char> fragment_shader_error(info_log_length); - glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, &fragment_shader_error[0]); - if (result == GL_TRUE) { - LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]); - } else { - LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s", - &fragment_shader_error[0]); - } +GLuint LoadShader(const char* source, GLenum type) { + const char* debug_type; + switch (type) { + case GL_VERTEX_SHADER: + debug_type = "vertex"; + break; + case GL_GEOMETRY_SHADER: + debug_type = "geometry"; + break; + case GL_FRAGMENT_SHADER: + debug_type = "fragment"; + break; + default: + UNREACHABLE(); } + GLuint shader_id = glCreateShader(type); + glShaderSource(shader_id, 1, &source, nullptr); + NGLOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type); + glCompileShader(shader_id); - // Link the program - LOG_DEBUG(Render_OpenGL, "Linking program..."); - - GLuint program_id = glCreateProgram(); - glAttachShader(program_id, vertex_shader_id); - glAttachShader(program_id, fragment_shader_id); - - glLinkProgram(program_id); - - // Check the program - glGetProgramiv(program_id, GL_LINK_STATUS, &result); - glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length); + GLint result = GL_FALSE; + GLint info_log_length; + glGetShaderiv(shader_id, GL_COMPILE_STATUS, &result); + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &info_log_length); if (info_log_length > 1) { - std::vector<char> program_error(info_log_length); - glGetProgramInfoLog(program_id, info_log_length, nullptr, &program_error[0]); + std::string shader_error(info_log_length, ' '); + glGetShaderInfoLog(shader_id, info_log_length, nullptr, &shader_error[0]); if (result == GL_TRUE) { - LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]); + NGLOG_DEBUG(Render_OpenGL, "{}", shader_error); } else { - LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]); + NGLOG_ERROR(Render_OpenGL, "Error compiling {} shader:\n{}", debug_type, shader_error); } } - - // If the program linking failed at least one of the shaders was probably bad - if (result == GL_FALSE) { - LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader); - LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader); - } - ASSERT_MSG(result == GL_TRUE, "Shader not linked"); - - glDeleteShader(vertex_shader_id); - glDeleteShader(fragment_shader_id); - - return program_id; + return shader_id; } } // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index a4bcffdfa..2036a06a9 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -4,16 +4,89 @@ #pragma once +#include <string> +#include <vector> #include <glad/glad.h> +#include "common/assert.h" +#include "common/logging/log.h" namespace GLShader { /** + * Utility function to log the source code of a list of shaders. + * @param shaders The OpenGL shaders whose source we will print. + */ +template <typename... T> +void LogShaderSource(T... shaders) { + auto shader_list = {shaders...}; + + for (const auto& shader : shader_list) { + if (shader == 0) + continue; + + GLint source_length; + glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length); + + std::string source(source_length, ' '); + glGetShaderSource(shader, source_length, nullptr, &source[0]); + NGLOG_INFO(Render_OpenGL, "Shader source {}", source); + } +} + +/** + * Utility function to create and compile an OpenGL GLSL shader + * @param source String of the GLSL shader program + * @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER) + */ +GLuint LoadShader(const char* source, GLenum type); + +/** * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader) - * @param vertex_shader String of the GLSL vertex shader program - * @param fragment_shader String of the GLSL fragment shader program - * @returns Handle of the newly created OpenGL shader object + * @param separable_program whether to create a separable program + * @param shaders ID of shaders to attach to the program + * @returns Handle of the newly created OpenGL program object */ -GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader); +template <typename... T> +GLuint LoadProgram(bool separable_program, T... shaders) { + // Link the program + NGLOG_DEBUG(Render_OpenGL, "Linking program..."); + + GLuint program_id = glCreateProgram(); + + ((shaders == 0 ? (void)0 : glAttachShader(program_id, shaders)), ...); + + if (separable_program) { + glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE); + } + + glLinkProgram(program_id); + + // Check the program + GLint result = GL_FALSE; + GLint info_log_length; + glGetProgramiv(program_id, GL_LINK_STATUS, &result); + glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (info_log_length > 1) { + std::string program_error(info_log_length, ' '); + glGetProgramInfoLog(program_id, info_log_length, nullptr, &program_error[0]); + if (result == GL_TRUE) { + NGLOG_DEBUG(Render_OpenGL, "{}", program_error); + } else { + NGLOG_ERROR(Render_OpenGL, "Error linking shader:\n{}", program_error); + } + } + + if (result == GL_FALSE) { + // There was a problem linking the shader, print the source for debugging purposes. + LogShaderSource(shaders...); + } + + ASSERT_MSG(result == GL_TRUE, "Shader not linked"); + + ((shaders == 0 ? (void)0 : glDetachShader(program_id, shaders)), ...); + + return program_id; +} } // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 5770ae08f..44f0c8a01 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <iterator> #include <glad/glad.h> -#include "common/common_funcs.h" #include "common/logging/log.h" #include "video_core/renderer_opengl/gl_state.h" @@ -33,7 +33,7 @@ OpenGLState::OpenGLState() { stencil.action_depth_pass = GL_KEEP; stencil.action_stencil_fail = GL_KEEP; - blend.enabled = false; + blend.enabled = true; blend.rgb_equation = GL_FUNC_ADD; blend.a_equation = GL_FUNC_ADD; blend.src_rgb_func = GL_ONE; @@ -50,6 +50,10 @@ OpenGLState::OpenGLState() { for (auto& texture_unit : texture_units) { texture_unit.texture_2d = 0; texture_unit.sampler = 0; + texture_unit.swizzle.r = GL_RED; + texture_unit.swizzle.g = GL_GREEN; + texture_unit.swizzle.b = GL_BLUE; + texture_unit.swizzle.a = GL_ALPHA; } lighting_lut.texture_buffer = 0; @@ -68,6 +72,18 @@ OpenGLState::OpenGLState() { draw.vertex_buffer = 0; draw.uniform_buffer = 0; draw.shader_program = 0; + draw.program_pipeline = 0; + + scissor.enabled = false; + scissor.x = 0; + scissor.y = 0; + scissor.width = 0; + scissor.height = 0; + + viewport.x = 0; + viewport.y = 0; + viewport.width = 0; + viewport.height = 0; clip_distance = {}; } @@ -148,9 +164,6 @@ void OpenGLState::Apply() const { if (blend.enabled != cur_state.blend.enabled) { if (blend.enabled) { glEnable(GL_BLEND); - - cur_state.logic_op = GL_COPY; - glLogicOp(cur_state.logic_op); glDisable(GL_COLOR_LOGIC_OP); } else { glDisable(GL_BLEND); @@ -183,20 +196,43 @@ void OpenGLState::Apply() const { } // Textures - for (unsigned i = 0; i < ARRAY_SIZE(texture_units); ++i) { + for (size_t i = 0; i < std::size(texture_units); ++i) { if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) { - glActiveTexture(TextureUnits::PicaTexture(i).Enum()); + glActiveTexture(TextureUnits::MaxwellTexture(i).Enum()); glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d); } if (texture_units[i].sampler != cur_state.texture_units[i].sampler) { glBindSampler(i, texture_units[i].sampler); } + // Update the texture swizzle + if (texture_units[i].swizzle.r != cur_state.texture_units[i].swizzle.r || + texture_units[i].swizzle.g != cur_state.texture_units[i].swizzle.g || + texture_units[i].swizzle.b != cur_state.texture_units[i].swizzle.b || + texture_units[i].swizzle.a != cur_state.texture_units[i].swizzle.a) { + std::array<GLint, 4> mask = {texture_units[i].swizzle.r, texture_units[i].swizzle.g, + texture_units[i].swizzle.b, texture_units[i].swizzle.a}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, mask.data()); + } + } + + // Constbuffers + for (u32 stage = 0; stage < draw.const_buffers.size(); ++stage) { + for (u32 buffer_id = 0; buffer_id < draw.const_buffers[stage].size(); ++buffer_id) { + auto& current = cur_state.draw.const_buffers[stage][buffer_id]; + auto& new_state = draw.const_buffers[stage][buffer_id]; + if (current.enabled != new_state.enabled || current.bindpoint != new_state.bindpoint || + current.ssbo != new_state.ssbo) { + if (new_state.enabled) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, new_state.bindpoint, new_state.ssbo); + } + } + } } // Lighting LUTs if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) { glActiveTexture(TextureUnits::LightingLUT.Enum()); - glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer); + glBindTexture(GL_TEXTURE_BUFFER, lighting_lut.texture_buffer); } // Fog LUT @@ -263,6 +299,31 @@ void OpenGLState::Apply() const { glUseProgram(draw.shader_program); } + // Program pipeline + if (draw.program_pipeline != cur_state.draw.program_pipeline) { + glBindProgramPipeline(draw.program_pipeline); + } + + // Scissor test + if (scissor.enabled != cur_state.scissor.enabled) { + if (scissor.enabled) { + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } + } + + if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y || + scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) { + glScissor(scissor.x, scissor.y, scissor.width, scissor.height); + } + + if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y || + viewport.width != cur_state.viewport.width || + viewport.height != cur_state.viewport.height) { + glViewport(viewport.x, viewport.y, viewport.width, viewport.height); + } + // Clip distance for (size_t i = 0; i < clip_distance.size(); ++i) { if (clip_distance[i] != cur_state.clip_distance[i]) { @@ -277,62 +338,75 @@ void OpenGLState::Apply() const { cur_state = *this; } -void OpenGLState::ResetTexture(GLuint handle) { - for (auto& unit : cur_state.texture_units) { +OpenGLState& OpenGLState::ResetTexture(GLuint handle) { + for (auto& unit : texture_units) { if (unit.texture_2d == handle) { unit.texture_2d = 0; } } - if (cur_state.lighting_lut.texture_buffer == handle) - cur_state.lighting_lut.texture_buffer = 0; - if (cur_state.fog_lut.texture_buffer == handle) - cur_state.fog_lut.texture_buffer = 0; - if (cur_state.proctex_noise_lut.texture_buffer == handle) - cur_state.proctex_noise_lut.texture_buffer = 0; - if (cur_state.proctex_color_map.texture_buffer == handle) - cur_state.proctex_color_map.texture_buffer = 0; - if (cur_state.proctex_alpha_map.texture_buffer == handle) - cur_state.proctex_alpha_map.texture_buffer = 0; - if (cur_state.proctex_lut.texture_buffer == handle) - cur_state.proctex_lut.texture_buffer = 0; - if (cur_state.proctex_diff_lut.texture_buffer == handle) - cur_state.proctex_diff_lut.texture_buffer = 0; + if (lighting_lut.texture_buffer == handle) + lighting_lut.texture_buffer = 0; + if (fog_lut.texture_buffer == handle) + fog_lut.texture_buffer = 0; + if (proctex_noise_lut.texture_buffer == handle) + proctex_noise_lut.texture_buffer = 0; + if (proctex_color_map.texture_buffer == handle) + proctex_color_map.texture_buffer = 0; + if (proctex_alpha_map.texture_buffer == handle) + proctex_alpha_map.texture_buffer = 0; + if (proctex_lut.texture_buffer == handle) + proctex_lut.texture_buffer = 0; + if (proctex_diff_lut.texture_buffer == handle) + proctex_diff_lut.texture_buffer = 0; + return *this; } -void OpenGLState::ResetSampler(GLuint handle) { - for (auto& unit : cur_state.texture_units) { +OpenGLState& OpenGLState::ResetSampler(GLuint handle) { + for (auto& unit : texture_units) { if (unit.sampler == handle) { unit.sampler = 0; } } + return *this; +} + +OpenGLState& OpenGLState::ResetProgram(GLuint handle) { + if (draw.shader_program == handle) { + draw.shader_program = 0; + } + return *this; } -void OpenGLState::ResetProgram(GLuint handle) { - if (cur_state.draw.shader_program == handle) { - cur_state.draw.shader_program = 0; +OpenGLState& OpenGLState::ResetPipeline(GLuint handle) { + if (draw.program_pipeline == handle) { + draw.program_pipeline = 0; } + return *this; } -void OpenGLState::ResetBuffer(GLuint handle) { - if (cur_state.draw.vertex_buffer == handle) { - cur_state.draw.vertex_buffer = 0; +OpenGLState& OpenGLState::ResetBuffer(GLuint handle) { + if (draw.vertex_buffer == handle) { + draw.vertex_buffer = 0; } - if (cur_state.draw.uniform_buffer == handle) { - cur_state.draw.uniform_buffer = 0; + if (draw.uniform_buffer == handle) { + draw.uniform_buffer = 0; } + return *this; } -void OpenGLState::ResetVertexArray(GLuint handle) { - if (cur_state.draw.vertex_array == handle) { - cur_state.draw.vertex_array = 0; +OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) { + if (draw.vertex_array == handle) { + draw.vertex_array = 0; } + return *this; } -void OpenGLState::ResetFramebuffer(GLuint handle) { - if (cur_state.draw.read_framebuffer == handle) { - cur_state.draw.read_framebuffer = 0; +OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { + if (draw.read_framebuffer == handle) { + draw.read_framebuffer = 0; } - if (cur_state.draw.draw_framebuffer == handle) { - cur_state.draw.draw_framebuffer = 0; + if (draw.draw_framebuffer == handle) { + draw.draw_framebuffer = 0; } + return *this; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 437fe34c4..839e50e93 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -16,7 +16,7 @@ struct TextureUnit { } }; -constexpr TextureUnit PicaTexture(int unit) { +constexpr TextureUnit MaxwellTexture(int unit) { return TextureUnit{unit}; } @@ -85,7 +85,13 @@ public: struct { GLuint texture_2d; // GL_TEXTURE_BINDING_2D GLuint sampler; // GL_SAMPLER_BINDING - } texture_units[3]; + struct { + GLint r; // GL_TEXTURE_SWIZZLE_R + GLint g; // GL_TEXTURE_SWIZZLE_G + GLint b; // GL_TEXTURE_SWIZZLE_B + GLint a; // GL_TEXTURE_SWIZZLE_A + } swizzle; + } texture_units[32]; struct { GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER @@ -122,27 +128,50 @@ public: GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM + GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING + struct ConstBufferConfig { + bool enabled = false; + GLuint bindpoint; + GLuint ssbo; + }; + std::array<std::array<ConstBufferConfig, 16>, 5> const_buffers{}; } draw; + struct { + bool enabled; // GL_SCISSOR_TEST + GLint x; + GLint y; + GLsizei width; + GLsizei height; + } scissor; + + struct { + GLint x; + GLint y; + GLsizei width; + GLsizei height; + } viewport; + std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE OpenGLState(); /// Get the currently active OpenGL state - static const OpenGLState& GetCurState() { + static OpenGLState GetCurState() { return cur_state; } /// Apply this state as the current OpenGL state void Apply() const; - /// Resets and unbinds any references to the given resource in the current OpenGL state - static void ResetTexture(GLuint handle); - static void ResetSampler(GLuint handle); - static void ResetProgram(GLuint handle); - static void ResetBuffer(GLuint handle); - static void ResetVertexArray(GLuint handle); - static void ResetFramebuffer(GLuint handle); + /// Resets any references to the given resource + OpenGLState& ResetTexture(GLuint handle); + OpenGLState& ResetSampler(GLuint handle); + OpenGLState& ResetProgram(GLuint handle); + OpenGLState& ResetPipeline(GLuint handle); + OpenGLState& ResetBuffer(GLuint handle); + OpenGLState& ResetVertexArray(GLuint handle); + OpenGLState& ResetFramebuffer(GLuint handle); private: static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp new file mode 100644 index 000000000..a2713e9f0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -0,0 +1,182 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <deque> +#include <vector> +#include "common/alignment.h" +#include "common/assert.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +class OrphanBuffer : public OGLStreamBuffer { +public: + explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~OrphanBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override; + void Unmap() override; + + std::vector<u8> data; +}; + +class StorageBuffer : public OGLStreamBuffer { +public: + explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~StorageBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override; + void Unmap() override; + + struct Fence { + OGLSync sync; + size_t offset; + }; + std::deque<Fence> head; + std::deque<Fence> tail; + + u8* mapped_ptr; +}; + +OGLStreamBuffer::OGLStreamBuffer(GLenum target) { + gl_target = target; +} + +GLuint OGLStreamBuffer::GetHandle() const { + return gl_buffer.handle; +} + +std::unique_ptr<OGLStreamBuffer> OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) { + if (storage_buffer) { + return std::make_unique<StorageBuffer>(target); + } + return std::make_unique<OrphanBuffer>(target); +} + +OrphanBuffer::~OrphanBuffer() { + Release(); +} + +void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) { + buffer_pos = 0; + buffer_size = size; + data.resize(buffer_size); + + if (gl_buffer.handle == 0) { + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + } + + glBufferData(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, GL_STREAM_DRAW); +} + +void OrphanBuffer::Release() { + gl_buffer.Release(); +} + +std::pair<u8*, GLintptr> OrphanBuffer::Map(size_t size, size_t alignment) { + buffer_pos = Common::AlignUp(buffer_pos, alignment); + + if (buffer_pos + size > buffer_size) { + Create(std::max(buffer_size, size), 0); + } + + mapped_size = size; + return std::make_pair(&data[buffer_pos], static_cast<GLintptr>(buffer_pos)); +} + +void OrphanBuffer::Unmap() { + glBufferSubData(gl_target, static_cast<GLintptr>(buffer_pos), + static_cast<GLsizeiptr>(mapped_size), &data[buffer_pos]); + buffer_pos += mapped_size; +} + +StorageBuffer::~StorageBuffer() { + Release(); +} + +void StorageBuffer::Create(size_t size, size_t sync_subdivide) { + if (gl_buffer.handle != 0) + return; + + buffer_pos = 0; + buffer_size = size; + buffer_sync_subdivide = std::max<size_t>(sync_subdivide, 1); + + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + + glBufferStorage(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); + mapped_ptr = reinterpret_cast<u8*>( + glMapBufferRange(gl_target, 0, static_cast<GLsizeiptr>(buffer_size), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); +} + +void StorageBuffer::Release() { + if (gl_buffer.handle == 0) + return; + + glUnmapBuffer(gl_target); + + gl_buffer.Release(); + head.clear(); + tail.clear(); +} + +std::pair<u8*, GLintptr> StorageBuffer::Map(size_t size, size_t alignment) { + ASSERT(size <= buffer_size); + + OGLSync sync; + + buffer_pos = Common::AlignUp(buffer_pos, alignment); + size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide); + + if (!head.empty() && + (effective_offset > head.back().offset || buffer_pos + size > buffer_size)) { + ASSERT(head.back().sync.handle == 0); + head.back().sync.Create(); + } + + if (buffer_pos + size > buffer_size) { + if (!tail.empty()) { + std::swap(sync, tail.back().sync); + tail.clear(); + } + std::swap(tail, head); + buffer_pos = 0; + effective_offset = 0; + } + + while (!tail.empty() && buffer_pos + size > tail.front().offset) { + std::swap(sync, tail.front().sync); + tail.pop_front(); + } + + if (sync.handle != 0) { + glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + sync.Release(); + } + + if (head.empty() || effective_offset > head.back().offset) { + head.emplace_back(); + head.back().offset = effective_offset; + } + + mapped_size = size; + return std::make_pair(&mapped_ptr[buffer_pos], static_cast<GLintptr>(buffer_pos)); +} + +void StorageBuffer::Unmap() { + glFlushMappedBufferRange(gl_target, static_cast<GLintptr>(buffer_pos), + static_cast<GLsizeiptr>(mapped_size)); + buffer_pos += mapped_size; +} diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h new file mode 100644 index 000000000..e78dc5784 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -0,0 +1,36 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <glad/glad.h> +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +class OGLStreamBuffer : private NonCopyable { +public: + explicit OGLStreamBuffer(GLenum target); + virtual ~OGLStreamBuffer() = default; + +public: + static std::unique_ptr<OGLStreamBuffer> MakeBuffer(bool storage_buffer, GLenum target); + + virtual void Create(size_t size, size_t sync_subdivide) = 0; + virtual void Release() {} + + GLuint GetHandle() const; + + virtual std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) = 0; + virtual void Unmap() = 0; + +protected: + OGLBuffer gl_buffer; + GLenum gl_target; + + size_t buffer_pos = 0; + size_t buffer_size = 0; + size_t buffer_sync_subdivide = 0; + size_t mapped_size = 0; +}; diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h new file mode 100644 index 000000000..2155fb019 --- /dev/null +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -0,0 +1,204 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <glad/glad.h> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" + +using GLvec2 = std::array<GLfloat, 2>; +using GLvec3 = std::array<GLfloat, 3>; +using GLvec4 = std::array<GLfloat, 4>; + +using GLuvec2 = std::array<GLuint, 2>; +using GLuvec3 = std::array<GLuint, 3>; +using GLuvec4 = std::array<GLuint, 4>; + +namespace MaxwellToGL { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +inline GLenum VertexType(Maxwell::VertexAttribute attrib) { + switch (attrib.type) { + case Maxwell::VertexAttribute::Type::UnsignedNorm: { + + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_8_8_8_8: + return GL_UNSIGNED_BYTE; + } + + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString()); + UNREACHABLE(); + return {}; + } + + case Maxwell::VertexAttribute::Type::SignedNorm: { + + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_8_8_8_8: + return GL_BYTE; + } + + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString()); + UNREACHABLE(); + return {}; + } + + case Maxwell::VertexAttribute::Type::Float: + return GL_FLOAT; + } + + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString()); + UNREACHABLE(); + return {}; +} + +inline GLenum IndexFormat(Maxwell::IndexFormat index_format) { + switch (index_format) { + case Maxwell::IndexFormat::UnsignedByte: + return GL_UNSIGNED_BYTE; + case Maxwell::IndexFormat::UnsignedShort: + return GL_UNSIGNED_SHORT; + case Maxwell::IndexFormat::UnsignedInt: + return GL_UNSIGNED_INT; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented index_format={}", static_cast<u32>(index_format)); + UNREACHABLE(); + return {}; +} + +inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { + switch (topology) { + case Maxwell::PrimitiveTopology::Triangles: + return GL_TRIANGLES; + case Maxwell::PrimitiveTopology::TriangleStrip: + return GL_TRIANGLE_STRIP; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology)); + UNREACHABLE(); + return {}; +} + +inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode) { + switch (filter_mode) { + case Tegra::Texture::TextureFilter::Linear: + return GL_LINEAR; + case Tegra::Texture::TextureFilter::Nearest: + return GL_NEAREST; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented texture filter mode={}", + static_cast<u32>(filter_mode)); + UNREACHABLE(); + return {}; +} + +inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { + switch (wrap_mode) { + case Tegra::Texture::WrapMode::Wrap: + return GL_REPEAT; + case Tegra::Texture::WrapMode::Mirror: + return GL_MIRRORED_REPEAT; + case Tegra::Texture::WrapMode::ClampToEdge: + return GL_CLAMP_TO_EDGE; + case Tegra::Texture::WrapMode::ClampOGL: + // TODO(Subv): GL_CLAMP was removed as of OpenGL 3.1, to implement GL_CLAMP, we can use + // 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; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented texture wrap mode={}", + static_cast<u32>(wrap_mode)); + UNREACHABLE(); + return {}; +} + +inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { + switch (equation) { + case Maxwell::Blend::Equation::Add: + return GL_FUNC_ADD; + case Maxwell::Blend::Equation::Subtract: + return GL_FUNC_SUBTRACT; + case Maxwell::Blend::Equation::ReverseSubtract: + return GL_FUNC_REVERSE_SUBTRACT; + case Maxwell::Blend::Equation::Min: + return GL_MIN; + case Maxwell::Blend::Equation::Max: + return GL_MAX; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented blend equation={}", static_cast<u32>(equation)); + UNREACHABLE(); + return {}; +} + +inline GLenum BlendFunc(Maxwell::Blend::Factor factor) { + switch (factor) { + case Maxwell::Blend::Factor::Zero: + return GL_ZERO; + case Maxwell::Blend::Factor::One: + return GL_ONE; + case Maxwell::Blend::Factor::SourceColor: + return GL_SRC_COLOR; + case Maxwell::Blend::Factor::OneMinusSourceColor: + return GL_ONE_MINUS_SRC_COLOR; + case Maxwell::Blend::Factor::SourceAlpha: + return GL_SRC_ALPHA; + case Maxwell::Blend::Factor::OneMinusSourceAlpha: + return GL_ONE_MINUS_SRC_ALPHA; + case Maxwell::Blend::Factor::DestAlpha: + return GL_DST_ALPHA; + case Maxwell::Blend::Factor::OneMinusDestAlpha: + return GL_ONE_MINUS_DST_ALPHA; + case Maxwell::Blend::Factor::DestColor: + return GL_DST_COLOR; + case Maxwell::Blend::Factor::OneMinusDestColor: + return GL_ONE_MINUS_DST_COLOR; + case Maxwell::Blend::Factor::SourceAlphaSaturate: + return GL_SRC_ALPHA_SATURATE; + case Maxwell::Blend::Factor::Source1Color: + return GL_SRC1_COLOR; + case Maxwell::Blend::Factor::OneMinusSource1Color: + return GL_ONE_MINUS_SRC1_COLOR; + case Maxwell::Blend::Factor::Source1Alpha: + return GL_SRC1_ALPHA; + case Maxwell::Blend::Factor::OneMinusSource1Alpha: + return GL_ONE_MINUS_SRC1_ALPHA; + case Maxwell::Blend::Factor::ConstantColor: + return GL_CONSTANT_COLOR; + case Maxwell::Blend::Factor::OneMinusConstantColor: + return GL_ONE_MINUS_CONSTANT_COLOR; + case Maxwell::Blend::Factor::ConstantAlpha: + return GL_CONSTANT_ALPHA; + case Maxwell::Blend::Factor::OneMinusConstantAlpha: + return GL_ONE_MINUS_CONSTANT_ALPHA; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented blend factor={}", static_cast<u32>(factor)); + UNREACHABLE(); + return {}; +} + +inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) { + switch (source) { + case Tegra::Texture::SwizzleSource::Zero: + return GL_ZERO; + case Tegra::Texture::SwizzleSource::R: + return GL_RED; + case Tegra::Texture::SwizzleSource::G: + return GL_GREEN; + case Tegra::Texture::SwizzleSource::B: + return GL_BLUE; + case Tegra::Texture::SwizzleSource::A: + return GL_ALPHA; + case Tegra::Texture::SwizzleSource::OneInt: + case Tegra::Texture::SwizzleSource::OneFloat: + return GL_ONE; + } + NGLOG_CRITICAL(Render_OpenGL, "Unimplemented swizzle source={}", static_cast<u32>(source)); + UNREACHABLE(); + return {}; +} + +} // namespace MaxwellToGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 7f921fa32..f33766bfd 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -9,17 +9,15 @@ #include <memory> #include <glad/glad.h> #include "common/assert.h" -#include "common/bit_field.h" #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" -#include "core/hw/hw.h" -#include "core/hw/lcd.h" #include "core/memory.h" #include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/utils.h" #include "video_core/video_core.h" static const char vertex_shader[] = R"( @@ -56,7 +54,7 @@ uniform sampler2D color_texture; void main() { // Swap RGBA -> ABGR so we don't have to do this on the CPU. This needs to change if we have to // support more framebuffer pixel formats. - color = texture(color_texture, frag_tex_coord).abgr; + color = texture(color_texture, frag_tex_coord); } )"; @@ -98,197 +96,89 @@ RendererOpenGL::RendererOpenGL() = default; RendererOpenGL::~RendererOpenGL() = default; /// Swap buffers (render frame) -void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) { +void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) { + Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); - if (framebuffer_info != boost::none) { - // If framebuffer_info is provided, reload it from memory to a texture - if (screen_info.texture.width != (GLsizei)framebuffer_info->width || - screen_info.texture.height != (GLsizei)framebuffer_info->height || - screen_info.texture.pixel_format != framebuffer_info->pixel_format) { + if (framebuffer != boost::none) { + // If framebuffer is provided, reload it from memory to a texture + if (screen_info.texture.width != (GLsizei)framebuffer->width || + screen_info.texture.height != (GLsizei)framebuffer->height || + screen_info.texture.pixel_format != framebuffer->pixel_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info); + ConfigureFramebufferTexture(screen_info.texture, *framebuffer); } - LoadFBToScreenInfo(*framebuffer_info, screen_info); - } - DrawScreens(); - - Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Load the framebuffer from memory, draw it to the screen, and swap buffers + LoadFBToScreenInfo(*framebuffer, screen_info); + DrawScreen(); + render_window->SwapBuffers(); + } - // Swap buffers render_window->PollEvents(); - render_window->SwapBuffers(); Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); Core::System::GetInstance().perf_stats.BeginSystemFrame(); + // Restore the rasterizer state prev_state.Apply(); RefreshRasterizerSetting(); } -static inline u32 MortonInterleave128(u32 x, u32 y) { - // 128x128 Z-Order coordinate from 2D coordinates - static constexpr u32 xlut[] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, - 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, - 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, - 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, - 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, - 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, - 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, - 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, - 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, - 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, - 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, - 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, - 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, - 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, - 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, - 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, - 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, - 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, - 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, - 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, - 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, - 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, - 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, - 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, - 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, - 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, - 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, - 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, - 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, - 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, - 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, - 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, - 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, - 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, - }; - static constexpr u32 ylut[] = { - 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, - 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, - 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, - 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, - 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, - 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, - 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, - 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, - 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, - 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, - 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, - 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, - 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, - 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, - 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, - 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, - 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, - 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, - 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, - 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, - 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, - 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, - 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, - 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, - 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, - 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, - 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, - 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, - 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, - 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, - 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, - 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, - 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, - 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, - 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, - }; - return xlut[x % 128] + ylut[y % 128]; -} - -static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { - // Calculates the offset of the position of the pixel in Morton order - // Framebuffer images are split into 128x128 tiles. - - const unsigned int block_height = 128; - const unsigned int coarse_x = x & ~127; - - u32 i = MortonInterleave128(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - -static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, - u8* morton_data, u8* gl_data, bool morton_to_gl) { - u8* data_ptrs[2]; - for (unsigned y = 0; y < height; ++y) { - for (unsigned x = 0; x < width; ++x) { - const u32 coarse_y = y & ~127; - u32 morton_offset = - GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; - u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; - - data_ptrs[morton_to_gl] = morton_data + morton_offset; - data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; - - memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - /** * Loads framebuffer from emulated memory into the active OpenGL texture. */ -void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, +void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info) { - const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)}; - const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp}; - - MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4, - Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(), - true); + const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)}; + const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; + const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; - LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes, - framebuffer_info.address, framebuffer_info.width, framebuffer_info.height, - (int)framebuffer_info.pixel_format); + // Framebuffer orientation handling + framebuffer_transform_flags = framebuffer.transform_flags; // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default // only allows rows to have a memory alignement of 4. - ASSERT(framebuffer_info.stride % 4 == 0); + ASSERT(framebuffer.stride % 4 == 0); - framebuffer_flip_vertical = framebuffer_info.flip_vertical; + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride, + screen_info)) { + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; + screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); - // Reset the screen info's display texture to its own permanent texture - screen_info.display_texture = screen_info.texture.resource.handle; - screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); + Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes, + Memory::FlushMode::Flush); - // Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes); + VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4, + Memory::GetPointer(framebuffer_addr), + gl_framebuffer_data.data(), true); - state.texture_units[0].texture_2d = screen_info.texture.resource.handle; - state.Apply(); + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride); + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that - // they differ from the LCD resolution. - // TODO: Applications could theoretically crash Citra here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height, - screen_info.texture.gl_format, screen_info.texture.gl_type, - gl_framebuffer_data.data()); + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that + // they differ from the LCD resolution. + // TODO: Applications could theoretically crash yuzu here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, + gl_framebuffer_data.data()); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - state.texture_units[0].texture_2d = 0; - state.Apply(); + state.texture_units[0].texture_2d = 0; + state.Apply(); + } } /** @@ -318,7 +208,7 @@ void RendererOpenGL::InitOpenGLObjects() { 0.0f); // Link shaders and get variable locations - shader.Create(vertex_shader, fragment_shader); + shader.CreateFromSource(vertex_shader, nullptr, fragment_shader); state.draw.shader_program = shader.handle; state.Apply(); uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); @@ -372,22 +262,21 @@ void RendererOpenGL::InitOpenGLObjects() { } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, - const FramebufferInfo& framebuffer_info) { + const Tegra::FramebufferConfig& framebuffer) { - texture.width = framebuffer_info.width; - texture.height = framebuffer_info.height; + texture.width = framebuffer.width; + texture.height = framebuffer.height; GLint internal_format; - switch (framebuffer_info.pixel_format) { - case FramebufferInfo::PixelFormat::ABGR8: - // Use RGBA8 and swap in the fragment shader + switch (framebuffer.pixel_format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: internal_format = GL_RGBA; texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_INT_8_8_8_8; + texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; gl_framebuffer_data.resize(texture.width * texture.height * 4); break; default: - UNIMPLEMENTED(); + UNREACHABLE(); } state.texture_units[0].texture_2d = texture.resource.handle; @@ -401,20 +290,33 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, state.Apply(); } -void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, - float h) { +void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, + float h) { const auto& texcoords = screen_info.display_texcoords; - const auto& left = framebuffer_flip_vertical ? texcoords.right : texcoords.left; - const auto& right = framebuffer_flip_vertical ? texcoords.left : texcoords.right; + auto left = texcoords.left; + auto right = texcoords.right; + if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset) { + if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) { + // Flip the framebuffer vertically + left = texcoords.right; + right = texcoords.left; + } else { + // Other transformations are unsupported + NGLOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}", + static_cast<u32>(framebuffer_transform_flags)); + UNIMPLEMENTED(); + } + } std::array<ScreenRectVertex, 4> vertices = {{ - ScreenRectVertex(x, y, texcoords.top, right), - ScreenRectVertex(x + w, y, texcoords.bottom, right), - ScreenRectVertex(x, y + h, texcoords.top, left), - ScreenRectVertex(x + w, y + h, texcoords.bottom, left), + ScreenRectVertex(x, y, texcoords.top, left), + ScreenRectVertex(x + w, y, texcoords.bottom, left), + ScreenRectVertex(x, y + h, texcoords.top, right), + ScreenRectVertex(x + w, y + h, texcoords.bottom, right), }}; state.texture_units[0].texture_2d = screen_info.display_texture; + state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; state.Apply(); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); @@ -427,7 +329,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl /** * Draws the emulated screens to the emulator window. */ -void RendererOpenGL::DrawScreens() { +void RendererOpenGL::DrawScreen() { const auto& layout = render_window->GetFramebufferLayout(); const auto& screen = layout.screen; @@ -443,8 +345,8 @@ void RendererOpenGL::DrawScreens() { glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(), - (float)screen.GetHeight()); + DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top, + (float)screen.GetWidth(), (float)screen.GetHeight()); m_current_frame++; } @@ -497,21 +399,22 @@ static const char* GetType(GLenum type) { static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user_param) { - Log::Level level; + const char format[] = "{} {} {}: {}"; + const char* const str_source = GetSource(source); + const char* const str_type = GetType(type); + switch (severity) { case GL_DEBUG_SEVERITY_HIGH: - level = Log::Level::Error; + NGLOG_ERROR(Render_OpenGL, format, str_source, str_type, id, message); break; case GL_DEBUG_SEVERITY_MEDIUM: - level = Log::Level::Warning; + NGLOG_WARNING(Render_OpenGL, format, str_source, str_type, id, message); break; case GL_DEBUG_SEVERITY_NOTIFICATION: case GL_DEBUG_SEVERITY_LOW: - level = Log::Level::Debug; + NGLOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message); break; } - LOG_GENERIC(Log::Class::Render_OpenGL, level, "%s %s %d: %s", GetSource(source), GetType(type), - id, message); } /// Initialize the renderer @@ -527,9 +430,9 @@ bool RendererOpenGL::Init() { const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))}; const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))}; - LOG_INFO(Render_OpenGL, "GL_VERSION: %s", gl_version); - LOG_INFO(Render_OpenGL, "GL_VENDOR: %s", gpu_vendor); - LOG_INFO(Render_OpenGL, "GL_RENDERER: %s", gpu_model); + NGLOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version); + NGLOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor); + NGLOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor); Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 05bb3c5cf..2cc6d9a00 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -14,17 +14,17 @@ class EmuWindow; -/// Structure used for storing information about the textures for each 3DS screen +/// Structure used for storing information about the textures for the Switch screen struct TextureInfo { OGLTexture resource; GLsizei width; GLsizei height; GLenum gl_format; GLenum gl_type; - RendererBase::FramebufferInfo::PixelFormat pixel_format; + Tegra::FramebufferConfig::PixelFormat pixel_format; }; -/// Structure used for storing information about the display target for each 3DS screen +/// Structure used for storing information about the display target for the Switch screen struct ScreenInfo { GLuint display_texture; MathUtil::Rectangle<float> display_texcoords; @@ -37,7 +37,7 @@ public: ~RendererOpenGL() override; /// Swap buffers (render frame) - void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override; + void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override; /** * Set the emulator window to use for renderer @@ -53,13 +53,14 @@ public: private: void InitOpenGLObjects(); - void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info); - void DrawScreens(); - void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h); + void ConfigureFramebufferTexture(TextureInfo& texture, + const Tegra::FramebufferConfig& framebuffer); + void DrawScreen(); + void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); // Loads framebuffer from emulated memory into the display information structure - void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info); + void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info); // Fills active OpenGL texture with the given RGBA color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture); @@ -71,7 +72,7 @@ private: // OpenGL object IDs OGLVertexArray vertex_array; OGLBuffer vertex_buffer; - OGLShader shader; + OGLProgram shader; /// Display information for Switch screen ScreenInfo screen_info; @@ -87,6 +88,6 @@ private: GLuint attrib_position; GLuint attrib_tex_coord; - /// Flips the framebuffer vertically when true - bool framebuffer_flip_vertical; + /// Used for transforming the framebuffer orientation + Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; }; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp new file mode 100644 index 000000000..7bf9c4c4b --- /dev/null +++ b/src/video_core/textures/decoders.cpp @@ -0,0 +1,136 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "core/memory.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Calculates the offset of an (x, y) position within a swizzled texture. + * Taken from the Tegra X1 TRM. + */ +static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { + u32 image_width_in_gobs = image_width * bytes_per_pixel / 64; + u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (x * bytes_per_pixel / 64) * 512 * block_height + + (y % (8 * block_height) / 8) * 512; + x *= bytes_per_pixel; + u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); + + return address; +} + +void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); + u32 pixel_index = (x + y * width) * out_bytes_per_pixel; + + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +u32 BytesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::DXT1: + case TextureFormat::DXN1: + // In this case a 'pixel' actually refers to a 4x4 tile. + return 8; + case TextureFormat::DXT23: + case TextureFormat::DXT45: + // In this case a 'pixel' actually refers to a 4x4 tile. + return 16; + case TextureFormat::A8R8G8B8: + case TextureFormat::A2B10G10R10: + case TextureFormat::BF10GF11RF11: + return 4; + case TextureFormat::A1B5G5R5: + case TextureFormat::B5G6R5: + return 2; + case TextureFormat::R8: + return 1; + case TextureFormat::R16_G16_B16_A16: + return 8; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } +} + +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height, + u32 block_height) { + u8* data = Memory::GetPointer(address); + u32 bytes_per_pixel = BytesPerPixel(format); + + std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); + + switch (format) { + case TextureFormat::DXT1: + case TextureFormat::DXT23: + case TextureFormat::DXT45: + case TextureFormat::DXN1: + // In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel + // values. + CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, block_height); + break; + case TextureFormat::A8R8G8B8: + case TextureFormat::A2B10G10R10: + case TextureFormat::A1B5G5R5: + case TextureFormat::B5G6R5: + case TextureFormat::R8: + case TextureFormat::R16_G16_B16_A16: + case TextureFormat::BF10GF11RF11: + CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, block_height); + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return unswizzled_data; +} + +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height) { + std::vector<u8> rgba_data; + + // TODO(Subv): Implement. + switch (format) { + case TextureFormat::DXT1: + case TextureFormat::DXT23: + case TextureFormat::DXT45: + case TextureFormat::DXN1: + case TextureFormat::A8R8G8B8: + case TextureFormat::A2B10G10R10: + case TextureFormat::A1B5G5R5: + case TextureFormat::B5G6R5: + case TextureFormat::R8: + case TextureFormat::BF10GF11RF11: + // TODO(Subv): For the time being just forward the same data without any decoding. + rgba_data = texture_data; + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return rgba_data; +} + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h new file mode 100644 index 000000000..2562c4b06 --- /dev/null +++ b/src/video_core/textures/decoders.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Unswizzles a swizzled texture without changing its format. + */ +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height, + u32 block_height = TICEntry::DefaultBlockHeight); + +/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary. +void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height); + +/** + * Decodes an unswizzled texture into a A8R8G8B8 texture. + */ +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h new file mode 100644 index 000000000..a17eaf19d --- /dev/null +++ b/src/video_core/textures/texture.h @@ -0,0 +1,264 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +namespace Texture { + +enum class TextureFormat : u32 { + R32_G32_B32_A32 = 0x01, + R32_G32_B32 = 0x02, + R16_G16_B16_A16 = 0x03, + R32_G32 = 0x04, + R32_B24G8 = 0x05, + ETC2_RGB = 0x06, + X8B8G8R8 = 0x07, + A8R8G8B8 = 0x08, + A2B10G10R10 = 0x09, + ETC2_RGB_PTA = 0x0a, + ETC2_RGBA = 0x0b, + R16_G16 = 0x0c, + G8R24 = 0x0d, + G24R8 = 0x0e, + R32 = 0x0f, + BC6H_SF16 = 0x10, + BC6H_UF16 = 0x11, + A4B4G4R4 = 0x12, + A5B5G5R1 = 0x13, + A1B5G5R5 = 0x14, + B5G6R5 = 0x15, + B6G5R5 = 0x16, + BC7U = 0x17, + G8R8 = 0x18, + EAC = 0x19, + EACX2 = 0x1a, + R16 = 0x1b, + Y8_VIDEO = 0x1c, + R8 = 0x1d, + G4R4 = 0x1e, + R1 = 0x1f, + E5B9G9R9_SHAREDEXP = 0x20, + BF10GF11RF11 = 0x21, + G8B8G8R8 = 0x22, + B8G8R8G8 = 0x23, + DXT1 = 0x24, + DXT23 = 0x25, + DXT45 = 0x26, + DXN1 = 0x27, + DXN2 = 0x28, + Z24S8 = 0x29, + X8Z24 = 0x2a, + S8Z24 = 0x2b, + X4V4Z24__COV4R4V = 0x2c, + X4V4Z24__COV8R8V = 0x2d, + V8Z24__COV4R12V = 0x2e, + ZF32 = 0x2f, + ZF32_X24S8 = 0x30, + X8Z24_X20V4S8__COV4R4V = 0x31, + X8Z24_X20V4S8__COV8R8V = 0x32, + ZF32_X20V4X8__COV4R4V = 0x33, + ZF32_X20V4X8__COV8R8V = 0x34, + ZF32_X20V4S8__COV4R4V = 0x35, + ZF32_X20V4S8__COV8R8V = 0x36, + X8Z24_X16V8S8__COV4R12V = 0x37, + ZF32_X16V8X8__COV4R12V = 0x38, + ZF32_X16V8S8__COV4R12V = 0x39, + Z16 = 0x3a, + V8Z24__COV8R24V = 0x3b, + X8Z24_X16V8S8__COV8R24V = 0x3c, + ZF32_X16V8X8__COV8R24V = 0x3d, + ZF32_X16V8S8__COV8R24V = 0x3e, + ASTC_2D_4X4 = 0x40, + ASTC_2D_5X5 = 0x41, + ASTC_2D_6X6 = 0x42, + ASTC_2D_8X8 = 0x44, + ASTC_2D_10X10 = 0x45, + ASTC_2D_12X12 = 0x46, + ASTC_2D_5X4 = 0x50, + ASTC_2D_6X5 = 0x51, + ASTC_2D_8X6 = 0x52, + ASTC_2D_10X8 = 0x53, + ASTC_2D_12X10 = 0x54, + ASTC_2D_8X5 = 0x55, + ASTC_2D_10X5 = 0x56, + ASTC_2D_10X6 = 0x57, +}; + +enum class TextureType : u32 { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCubemap = 3, + Texture1DArray = 4, + Texture2DArray = 5, + Texture1DBuffer = 6, + Texture2DNoMipmap = 7, + TextureCubeArray = 8, +}; + +enum class TICHeaderVersion : u32 { + OneDBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4, +}; + +enum class ComponentType : u32 { + SNORM = 1, + UNORM = 2, + SINT = 3, + UINT = 4, + SNORM_FORCE_FP16 = 5, + UNORM_FORCE_FP16 = 6, + FLOAT = 7 +}; + +enum class SwizzleSource : u32 { + Zero = 0, + + R = 2, + G = 3, + B = 4, + A = 5, + OneInt = 6, + OneFloat = 7, +}; + +union TextureHandle { + u32 raw; + BitField<0, 20, u32> tic_id; + BitField<20, 12, u32> tsc_id; +}; +static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); + +struct TICEntry { + static constexpr u32 DefaultBlockHeight = 16; + + union { + u32 raw; + BitField<0, 7, TextureFormat> format; + BitField<7, 3, ComponentType> r_type; + BitField<10, 3, ComponentType> g_type; + BitField<13, 3, ComponentType> b_type; + BitField<16, 3, ComponentType> a_type; + + BitField<19, 3, SwizzleSource> x_source; + BitField<22, 3, SwizzleSource> y_source; + BitField<25, 3, SwizzleSource> z_source; + BitField<28, 3, SwizzleSource> w_source; + }; + u32 address_low; + union { + BitField<0, 16, u32> address_high; + BitField<21, 3, TICHeaderVersion> header_version; + }; + union { + BitField<3, 3, u32> block_height; + + // High 16 bits of the pitch value + BitField<0, 16, u32> pitch_high; + }; + union { + BitField<0, 16, u32> width_minus_1; + BitField<23, 4, TextureType> texture_type; + }; + u16 height_minus_1; + INSERT_PADDING_BYTES(10); + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); + } + + u32 Pitch() const { + ASSERT(header_version == TICHeaderVersion::Pitch || + header_version == TICHeaderVersion::PitchColorKey); + // The pitch value is 21 bits, and is 32B aligned. + return pitch_high << 5; + } + + u32 Width() const { + return width_minus_1 + 1; + } + + u32 Height() const { + return height_minus_1 + 1; + } + + u32 BlockHeight() const { + ASSERT(header_version == TICHeaderVersion::BlockLinear || + header_version == TICHeaderVersion::BlockLinearColorKey); + // The block height is stored in log2 format. + return 1 << block_height; + } + + bool IsTiled() const { + return header_version == TICHeaderVersion::BlockLinear || + header_version == TICHeaderVersion::BlockLinearColorKey; + } +}; +static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); + +enum class WrapMode : u32 { + Wrap = 0, + Mirror = 1, + ClampToEdge = 2, + Border = 3, + ClampOGL = 4, + MirrorOnceClampToEdge = 5, + MirrorOnceBorder = 6, + MirrorOnceClampOGL = 7, +}; + +enum class TextureFilter : u32 { + Nearest = 1, + Linear = 2, +}; + +enum class TextureMipmapFilter : u32 { + None = 1, + Nearest = 2, + Linear = 3, +}; + +struct TSCEntry { + union { + BitField<0, 3, WrapMode> wrap_u; + BitField<3, 3, WrapMode> wrap_v; + BitField<6, 3, WrapMode> wrap_p; + BitField<9, 1, u32> depth_compare_enabled; + BitField<10, 3, u32> depth_compare_func; + }; + union { + BitField<0, 2, TextureFilter> mag_filter; + BitField<4, 2, TextureFilter> min_filter; + BitField<6, 2, TextureMipmapFilter> mip_filter; + }; + INSERT_PADDING_BYTES(8); + u32 border_color_r; + u32 border_color_g; + u32 border_color_b; + u32 border_color_a; +}; +static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size"); + +struct FullTextureInfo { + u32 index; + TICEntry tic; + TSCEntry tsc; + bool enabled; +}; + +/// Returns the number of bytes per pixel of the input texture format. +u32 BytesPerPixel(TextureFormat format); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/utils.h b/src/video_core/utils.h index d94a10417..e0a14d48f 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { return (i + offset) * bytes_per_pixel; } +static inline u32 MortonInterleave128(u32 x, u32 y) { + // 128x128 Z-Order coordinate from 2D coordinates + static constexpr u32 xlut[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, + 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, + 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, + 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, + 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, + 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, + 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, + 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, + 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, + 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, + 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, + 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, + 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, + 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, + 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, + 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, + 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, + 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, + 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, + 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, + 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, + 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, + 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, + 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, + 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, + 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, + 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, + 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, + 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, + 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, + 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, + 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, + 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, + 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, + }; + static constexpr u32 ylut[] = { + 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, + 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, + 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, + 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, + 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, + 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, + 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, + 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, + 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, + 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, + 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, + 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, + 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, + 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, + 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, + 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, + 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, + 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, + 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, + 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, + 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, + 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, + 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, + 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, + 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, + 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, + 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, + 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, + 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, + 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, + 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, + 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, + 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, + 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, + 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, + }; + return xlut[x % 128] + ylut[y % 128]; +} + +static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { + // Calculates the offset of the position of the pixel in Morton order + // Framebuffer images are split into 128x128 tiles. + + const unsigned int block_height = 128; + const unsigned int coarse_x = x & ~127; + + u32 i = MortonInterleave128(x, y); + + const unsigned int offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + +static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, + u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, + bool morton_to_gl) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~127; + u32 morton_offset = + GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + y * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + } // namespace VideoCore diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 864691baa..89dc8ed1e 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -24,9 +24,9 @@ bool Init(EmuWindow* emu_window) { g_renderer = std::make_unique<RendererOpenGL>(); g_renderer->SetWindow(g_emu_window); if (g_renderer->Init()) { - LOG_DEBUG(Render, "initialized OK"); + NGLOG_DEBUG(Render, "initialized OK"); } else { - LOG_ERROR(Render, "initialization failed !"); + NGLOG_CRITICAL(Render, "initialization failed !"); return false; } return true; @@ -36,7 +36,7 @@ bool Init(EmuWindow* emu_window) { void Shutdown() { g_renderer.reset(); - LOG_DEBUG(Render, "shutdown OK"); + NGLOG_DEBUG(Render, "shutdown OK"); } } // namespace VideoCore diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 1fd90b9d0..37da62436 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -15,6 +15,8 @@ class RendererBase; namespace VideoCore { +enum class Renderer { Software, OpenGL }; + extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin extern EmuWindow* g_emu_window; ///< Emu window diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0c4056c49..c662570d2 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -23,10 +23,15 @@ add_executable(yuzu configuration/configure_input.h configuration/configure_system.cpp configuration/configure_system.h + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_surface.h debugger/profiler.cpp debugger/profiler.h - debugger/registers.cpp - debugger/registers.h debugger/wait_tree.cpp debugger/wait_tree.h game_list.cpp @@ -53,7 +58,6 @@ set(UIS configuration/configure_graphics.ui configuration/configure_input.ui configuration/configure_system.ui - debugger/registers.ui hotkeys.ui main.ui ) diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index da3429822..d6647eeea 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QIcon> #include "common/scm_rev.h" #include "ui_aboutdialog.h" #include "yuzu/about_dialog.h" AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); + ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 469988d63..5c17cd0d9 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -4,6 +4,8 @@ #include <QScreen> #include <QWindow> +#include <fmt/format.h> + #include "common/microprofile.h" #include "common/scm_rev.h" #include "common/string_util.h" @@ -102,8 +104,8 @@ private: GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : QWidget(parent), child(nullptr), emu_thread(emu_thread) { - std::string window_title = Common::StringFromFormat("yuzu %s| %s-%s", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); InputCommon::Init(); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index f9ddb9edc..8316db708 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -77,8 +77,8 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt()); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); + Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -94,6 +94,10 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); + qt_config->beginGroup("System"); + Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); qt_config->endGroup(); @@ -104,6 +108,8 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("UI"); + UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -171,7 +177,8 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core)); + qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); + qt_config->setValue("use_multi_core", Settings::values.use_multi_core); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -188,6 +195,10 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); + qt_config->beginGroup("System"); + qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 92fd6ab02..baa558667 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,9 +13,15 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->setupUi(this); + for (auto theme : UISettings::themes) { + ui->theme_combobox->addItem(theme.first, theme.second); + } + this->setConfiguration(); - ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_multi_core->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } ConfigureGeneral::~ConfigureGeneral() {} @@ -23,13 +29,20 @@ ConfigureGeneral::~ConfigureGeneral() {} void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); - ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core)); + ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); + ui->use_multi_core->setChecked(Settings::values.use_multi_core); + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex()); + UISettings::values.theme = + ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + + Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); + Settings::values.use_multi_core = ui->use_multi_core->isChecked(); + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 573c4cb0e..233adbe27 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -13,17 +13,17 @@ <property name="windowTitle"> <string>Form</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="HorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="VerticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="GeneralGroupBox"> <property name="title"> <string>General</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> @@ -44,40 +44,87 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>CPU Core</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QComboBox" name="cpu_core_combobox"> - <item> - <property name="text"> - <string>Unicorn</string> - </property> - </item> - <item> - <property name="text"> - <string>Dynarmic</string> - </property> - </item> - </widget> - </item> - </layout> - </item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_cpu_jit"> + <property name="text"> + <string>Enable CPU JIT</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="use_multi_core"> + <property name="text"> + <string>Enable multi-core</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="EmulationGroupBox"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="EmulationVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Enable docked mode</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="theme_group_box"> + <property name="title"> + <string>Theme</string> + </property> + <layout class="QHBoxLayout" name="theme_qhbox_layout"> + <item> + <layout class="QVBoxLayout" name="theme_qvbox_layout"> + <item> + <layout class="QHBoxLayout" name="theme_qhbox_layout_2"> + <item> + <widget class="QLabel" name="theme_label"> + <property name="text"> + <string>Theme:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="theme_combobox"/> + </item> + </layout> + </item> </layout> - </widget> + </item> + </layout> + </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> + <widget class="QGroupBox" name="HotKeysGroupBox"> <property name="title"> <string>Hotkeys</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <layout class="QHBoxLayout" name="HotKeysHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="HotKeysVerticalLayout"> <item> <widget class="GHotkeysDialog" name="widget" native="true"/> </item> diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..d6d61a739 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp @@ -0,0 +1,27 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMetaType> +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) { + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnMaxwellResume() { + emit Resumed(); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h new file mode 100644 index 000000000..9d05493cf --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. + * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, + protected Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +private slots: + virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp new file mode 100644 index 000000000..f98cc8152 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -0,0 +1,212 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_breakpoints_p.h" + +BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context, + QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) {} + +int BreakPointModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const { + return static_cast<int>(Tegra::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::DisplayRole: { + if (index.column() == 0) { + static const std::map<Tegra::DebugContext::Event, QString> map = { + {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, + {Tegra::DebugContext::Event::MaxwellCommandProcessed, + tr("Maxwell command processed")}, + {Tegra::DebugContext::Event::IncomingPrimitiveBatch, + tr("Incoming primitive batch")}, + {Tegra::DebugContext::Event::FinishedPrimitiveBatch, + tr("Finished primitive batch")}, + }; + + DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); + return (map.find(event) != map.end()) ? map.at(event) : QString(); + } + + break; + } + + case Qt::CheckStateRole: { + if (index.column() == 0) + return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; + break; + } + + case Qt::BackgroundRole: { + if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: { + auto context = context_weak.lock(); + return context && context->breakpoints[(int)event].enabled; + } + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { + if (!index.isValid()) + return 0; + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.column() == 0) + flags |= Qt::ItemIsUserCheckable; + return flags; +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::CheckStateRole: { + if (index.column() != 0) + return false; + + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[(int)event].enabled = value == Qt::Checked; + QModelIndex changed_index = createIndex(index.row(), 0); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + +void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(event), 0), + createIndex(static_cast<int>(event), 0)); +} + +void BreakPointModel::OnResumed() { + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), + createIndex(static_cast<int>(active_breakpoint), 0)); + active_breakpoint = context->active_breakpoint; +} + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( + std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) + : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( + debug_context) { + setObjectName("TegraBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setRootIsDecorated(false); + breakpoint_list->setHeaderHidden(true); + breakpoint_list->setModel(breakpoint_model); + + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, + SLOT(OnItemDoubleClicked(const QModelIndex&))); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnMaxwellResume() { + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() { + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() { + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { + if (!index.isValid()) + return; + + QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); + QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); + QVariant new_state = Qt::Unchecked; + if (enabled == Qt::Unchecked) + new_state = Qt::Checked; + breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h new file mode 100644 index 000000000..ae0ede2e8 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h @@ -0,0 +1,46 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + +public: + explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnItemDoubleClicked(const QModelIndex&); + void OnResumeRequested(); + void OnResumed(); + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + QLabel* status_text; + QPushButton* resume_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h new file mode 100644 index 000000000..35a6876ae --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h @@ -0,0 +1,36 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QAbstractListModel> +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr<Tegra::DebugContext> context_weak; + bool at_breakpoint; + Tegra::DebugContext::Event active_breakpoint; +}; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..1fbca8ad0 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,453 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QFileDialog> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QScrollArea> +#include <QSpinBox> +#include "core/core.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/utils.h" +#include "yuzu/debugger/graphics/graphics_surface.h" +#include "yuzu/util/spinbox.h" + +static Tegra::Texture::TextureFormat ConvertToTextureFormat( + Tegra::RenderTargetFormat render_target_format) { + switch (render_target_format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return Tegra::Texture::TextureFormat::A8R8G8B8; + case Tegra::RenderTargetFormat::RGB10_A2_UNORM: + return Tegra::Texture::TextureFormat::A2B10G10R10; + default: + UNIMPLEMENTED_MSG("Unimplemented RT format"); + } +} + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) + : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +void SurfacePicture::mousePressEvent(QMouseEvent* event) { + // Only do something while the left mouse button is held down + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (pixmap() == nullptr) + return; + + if (surface_widget) + surface_widget->Pick(event->x() * pixmap()->width() / width(), + event->y() * pixmap()->height() / height()); +} + +void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { + // We also want to handle the event if the user moves the mouse while holding down the LMB + mousePressEvent(event); +} + +GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), + surface_source(Source::RenderTarget0) { + setObjectName("MaxwellSurface"); + + surface_source_list = new QComboBox; + surface_source_list->addItem(tr("Render Target 0")); + surface_source_list->addItem(tr("Render Target 1")); + surface_source_list->addItem(tr("Render Target 2")); + surface_source_list->addItem(tr("Render Target 3")); + surface_source_list->addItem(tr("Render Target 4")); + surface_source_list->addItem(tr("Render Target 5")); + surface_source_list->addItem(tr("Render Target 6")); + surface_source_list->addItem(tr("Render Target 7")); + surface_source_list->addItem(tr("Z Buffer")); + surface_source_list->addItem(tr("Custom")); + surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); + + surface_address_control = new CSpinBox; + surface_address_control->SetBase(16); + surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); + surface_address_control->SetPrefix("0x"); + + unsigned max_dimension = 16384; // TODO: Find actual maximum + + surface_width_control = new QSpinBox; + surface_width_control->setRange(0, max_dimension); + + surface_height_control = new QSpinBox; + surface_height_control->setRange(0, max_dimension); + + surface_picker_x_control = new QSpinBox; + surface_picker_x_control->setRange(0, max_dimension - 1); + + surface_picker_y_control = new QSpinBox; + surface_picker_y_control->setRange(0, max_dimension - 1); + + surface_format_control = new QComboBox; + + // Color formats sorted by Maxwell texture format index + surface_format_control->addItem(tr("None")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("A8R8G8B8")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("DXT1")); + surface_format_control->addItem(tr("DXT23")); + surface_format_control->addItem(tr("DXT45")); + surface_format_control->addItem(tr("DXN1")); + surface_format_control->addItem(tr("DXN2")); + + surface_info_label = new QLabel(); + surface_info_label->setWordWrap(true); + + surface_picture_label = new SurfacePicture(0, this); + surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + surface_picture_label->setScaledContents(false); + + auto scroll_area = new QScrollArea(); + scroll_area->setBackgroundRole(QPalette::Dark); + scroll_area->setWidgetResizable(false); + scroll_area->setWidget(surface_picture_label); + + save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceSourceChanged(int))); + connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, + SLOT(OnSurfaceAddressChanged(qint64))); + connect(surface_width_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceWidthChanged(int))); + connect(surface_height_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceHeightChanged(int))); + connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceFormatChanged(int))); + connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerXChanged(int))); + connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerYChanged(int))); + connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(surface_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("GPU Address:"))); + sub_layout->addWidget(surface_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(surface_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(surface_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(surface_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(scroll_area); + + auto info_layout = new QHBoxLayout; + { + auto xy_layout = new QVBoxLayout; + { + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("X:"))); + sub_layout->addWidget(surface_picker_x_control); + xy_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Y:"))); + sub_layout->addWidget(surface_picker_y_control); + xy_layout->addLayout(sub_layout); + } + } + info_layout->addLayout(xy_layout); + surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + info_layout->addWidget(surface_info_label); + } + main_layout->addLayout(info_layout); + + main_layout->addWidget(save_surface); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + if (debug_context && debug_context->at_breakpoint) { + emit Update(); + widget()->setEnabled(debug_context->at_breakpoint); + } else { + widget()->setEnabled(false); + } +} + +void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsSurfaceWidget::OnResumed() { + widget()->setEnabled(false); +} + +void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { + surface_source = static_cast<Source>(new_value); + emit Update(); +} + +void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { + if (surface_address != new_value) { + surface_address = static_cast<Tegra::GPUVAddr>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { + if (surface_width != static_cast<unsigned>(new_value)) { + surface_width = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { + if (surface_height != static_cast<unsigned>(new_value)) { + surface_height = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { + if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { + surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { + if (surface_picker_x != new_value) { + surface_picker_x = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { + if (surface_picker_y != new_value) { + surface_picker_y = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::Pick(int x, int y) { + surface_picker_x_control->setValue(x); + surface_picker_y_control->setValue(y); + + if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || + y >= static_cast<int>(surface_height)) { + surface_info_label->setText(tr("Pixel out of bounds")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + return; + } + + surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); +} + +void GraphicsSurfaceWidget::OnUpdate() { + auto& gpu = Core::System::GetInstance().GPU(); + + QPixmap pixmap; + + switch (surface_source) { + case Source::RenderTarget0: + case Source::RenderTarget1: + case Source::RenderTarget2: + case Source::RenderTarget3: + case Source::RenderTarget4: + case Source::RenderTarget5: + case Source::RenderTarget6: + case Source::RenderTarget7: { + // TODO: Store a reference to the registers in the debug context instead of accessing them + // directly... + + auto& registers = gpu.Get3DEngine().regs; + auto& rt = registers.rt[static_cast<size_t>(surface_source) - + static_cast<size_t>(Source::RenderTarget0)]; + + surface_address = rt.Address(); + surface_width = rt.width; + surface_height = rt.height; + if (rt.format != Tegra::RenderTargetFormat::NONE) { + surface_format = ConvertToTextureFormat(rt.format); + } + + break; + } + + case Source::Custom: { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast<int>(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); + + if (surface_address == 0) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address); + + auto unswizzled_data = + Tegra::Texture::UnswizzleTexture(*address, surface_format, surface_width, surface_height); + + auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, + surface_width, surface_height); + + surface_picture_label->show(); + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + Math::Vec4<u8> color; + color[0] = texture_data[x + y * surface_width + 0]; + color[1] = texture_data[x + y * surface_width + 1]; + color[2] = texture_data[x + y * surface_width + 2]; + color[3] = texture_data[x + y * surface_width + 3]; + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + pixmap = QPixmap::fromImage(decoded_image); + surface_picture_label->setPixmap(pixmap); + surface_picture_label->resize(pixmap.size()); + + // Update the info with pixel data + surface_picker_x_control->setEnabled(true); + surface_picker_y_control->setEnabled(true); + Pick(surface_picker_x, surface_picker_y); + + // Enable saving the converted pixmap to file + save_surface->setEnabled(true); +} + +void GraphicsSurfaceWidget::SaveSurface() { + QString png_filter = tr("Portable Network Graphic (*.png)"); + QString bin_filter = tr("Binary data (*.bin)"); + + QString selectedFilter; + QString filename = QFileDialog::getSaveFileName( + this, tr("Save Surface"), + QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), + QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + + if (filename.isEmpty()) { + // If the user canceled the dialog, don't save anything. + return; + } + + if (selectedFilter == png_filter) { + const QPixmap* pixmap = surface_picture_label->pixmap(); + ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + if (pixmap) + pixmap->save(&file, "PNG"); + } else if (selectedFilter == bin_filter) { + auto& gpu = Core::System::GetInstance().GPU(); + boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address); + + const u8* buffer = Memory::GetPointer(*address); + ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); + QByteArray data(reinterpret_cast<const char*>(buffer), size); + file.write(data); + } else { + UNREACHABLE_MSG("Unhandled filter selected"); + } +} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h new file mode 100644 index 000000000..6a344bdfc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -0,0 +1,97 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QLabel> +#include <QPushButton> +#include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +class QComboBox; +class QSpinBox; +class CSpinBox; + +class GraphicsSurfaceWidget; + +class SurfacePicture : public QLabel { + Q_OBJECT + +public: + explicit SurfacePicture(QWidget* parent = nullptr, + GraphicsSurfaceWidget* surface_widget = nullptr); + ~SurfacePicture(); + +protected slots: + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + GraphicsSurfaceWidget* surface_widget; +}; + +class GraphicsSurfaceWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + + enum class Source { + RenderTarget0 = 0, + RenderTarget1 = 1, + RenderTarget2 = 2, + RenderTarget3 = 3, + RenderTarget4 = 4, + RenderTarget5 = 5, + RenderTarget6 = 6, + RenderTarget7 = 7, + ZBuffer = 8, + Custom = 9, + }; + +public: + explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + void Pick(int x, int y); + +public slots: + void OnSurfaceSourceChanged(int new_value); + void OnSurfaceAddressChanged(qint64 new_value); + void OnSurfaceWidthChanged(int new_value); + void OnSurfaceHeightChanged(int new_value); + void OnSurfaceFormatChanged(int new_value); + void OnSurfacePickerXChanged(int new_value); + void OnSurfacePickerYChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnResumed() override; + + void SaveSurface(); + +signals: + void Update(); + +private: + QComboBox* surface_source_list; + CSpinBox* surface_address_control; + QSpinBox* surface_width_control; + QSpinBox* surface_height_control; + QComboBox* surface_format_control; + + SurfacePicture* surface_picture_label; + QSpinBox* surface_picker_x_control; + QSpinBox* surface_picker_y_control; + QLabel* surface_info_label; + QPushButton* save_surface; + + Source surface_source; + Tegra::GPUVAddr surface_address; + unsigned surface_width; + unsigned surface_height; + Tegra::Texture::TextureFormat surface_format; + int surface_picker_x = 0; + int surface_picker_y = 0; +}; diff --git a/src/yuzu/debugger/registers.cpp b/src/yuzu/debugger/registers.cpp deleted file mode 100644 index 06e2d1647..000000000 --- a/src/yuzu/debugger/registers.cpp +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QTreeWidgetItem> -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "yuzu/debugger/registers.h" -#include "yuzu/util/util.h" - -RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) { - cpu_regs_ui.setupUi(this); - - tree = cpu_regs_ui.treeWidget; - tree->addTopLevelItem(core_registers = new QTreeWidgetItem(QStringList(tr("Registers")))); - tree->addTopLevelItem(vfp_registers = new QTreeWidgetItem(QStringList(tr("VFP Registers")))); - tree->addTopLevelItem(vfp_system_registers = - new QTreeWidgetItem(QStringList(tr("VFP System Registers")))); - tree->addTopLevelItem(cpsr = new QTreeWidgetItem(QStringList("CPSR"))); - - for (int i = 0; i < 16; ++i) { - QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i))); - core_registers->addChild(child); - } - - for (int i = 0; i < 32; ++i) { - QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("S[%1]").arg(i))); - vfp_registers->addChild(child); - } - - QFont font = GetMonospaceFont(); - - CreateCPSRChildren(); - CreateVFPSystemRegisterChildren(); - - // Set Registers to display in monospace font - for (int i = 0; i < core_registers->childCount(); ++i) - core_registers->child(i)->setFont(1, font); - - for (int i = 0; i < vfp_registers->childCount(); ++i) - vfp_registers->child(i)->setFont(1, font); - - for (int i = 0; i < vfp_system_registers->childCount(); ++i) { - vfp_system_registers->child(i)->setFont(1, font); - for (int x = 0; x < vfp_system_registers->child(i)->childCount(); ++x) { - vfp_system_registers->child(i)->child(x)->setFont(1, font); - } - } - // Set CSPR to display in monospace font - cpsr->setFont(1, font); - for (int i = 0; i < cpsr->childCount(); ++i) { - cpsr->child(i)->setFont(1, font); - for (int x = 0; x < cpsr->child(i)->childCount(); ++x) { - cpsr->child(i)->child(x)->setFont(1, font); - } - } - setEnabled(false); -} - -void RegistersWidget::OnDebugModeEntered() { - if (!Core::System::GetInstance().IsPoweredOn()) - return; - - for (int i = 0; i < core_registers->childCount(); ++i) - core_registers->child(i)->setText( - 1, QString("0x%1").arg(Core::CPU().GetReg(i), 8, 16, QLatin1Char('0'))); - - UpdateCPSRValues(); -} - -void RegistersWidget::OnDebugModeLeft() {} - -void RegistersWidget::OnEmulationStarting(EmuThread* emu_thread) { - setEnabled(true); -} - -void RegistersWidget::OnEmulationStopping() { - // Reset widget text - for (int i = 0; i < core_registers->childCount(); ++i) - core_registers->child(i)->setText(1, QString("")); - - for (int i = 0; i < vfp_registers->childCount(); ++i) - vfp_registers->child(i)->setText(1, QString("")); - - for (int i = 0; i < cpsr->childCount(); ++i) - cpsr->child(i)->setText(1, QString("")); - - cpsr->setText(1, QString("")); - - // FPSCR - for (int i = 0; i < vfp_system_registers->child(0)->childCount(); ++i) - vfp_system_registers->child(0)->child(i)->setText(1, QString("")); - - // FPEXC - for (int i = 0; i < vfp_system_registers->child(1)->childCount(); ++i) - vfp_system_registers->child(1)->child(i)->setText(1, QString("")); - - vfp_system_registers->child(0)->setText(1, QString("")); - vfp_system_registers->child(1)->setText(1, QString("")); - vfp_system_registers->child(2)->setText(1, QString("")); - vfp_system_registers->child(3)->setText(1, QString("")); - - setEnabled(false); -} - -void RegistersWidget::CreateCPSRChildren() { - cpsr->addChild(new QTreeWidgetItem(QStringList("M"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("T"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("F"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("I"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("A"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("E"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("IT"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("GE"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("DNM"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("J"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("Q"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("V"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("C"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("Z"))); - cpsr->addChild(new QTreeWidgetItem(QStringList("N"))); -} - -void RegistersWidget::UpdateCPSRValues() { - const u32 cpsr_val = Core::CPU().GetCPSR(); - - cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0'))); - cpsr->child(0)->setText( - 1, QString("b%1").arg(cpsr_val & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode - cpsr->child(1)->setText(1, QString::number((cpsr_val >> 5) & 1)); // T - State - cpsr->child(2)->setText(1, QString::number((cpsr_val >> 6) & 1)); // F - FIQ disable - cpsr->child(3)->setText(1, QString::number((cpsr_val >> 7) & 1)); // I - IRQ disable - cpsr->child(4)->setText(1, QString::number((cpsr_val >> 8) & 1)); // A - Imprecise abort - cpsr->child(5)->setText(1, QString::number((cpsr_val >> 9) & 1)); // E - Data endianness - cpsr->child(6)->setText(1, - QString::number((cpsr_val >> 10) & 0x3F)); // IT - If-Then state (DNM) - cpsr->child(7)->setText(1, - QString::number((cpsr_val >> 16) & 0xF)); // GE - Greater-than-or-Equal - cpsr->child(8)->setText(1, QString::number((cpsr_val >> 20) & 0xF)); // DNM - Do not modify - cpsr->child(9)->setText(1, QString::number((cpsr_val >> 24) & 1)); // J - Jazelle - cpsr->child(10)->setText(1, QString::number((cpsr_val >> 27) & 1)); // Q - Saturation - cpsr->child(11)->setText(1, QString::number((cpsr_val >> 28) & 1)); // V - Overflow - cpsr->child(12)->setText(1, QString::number((cpsr_val >> 29) & 1)); // C - Carry/Borrow/Extend - cpsr->child(13)->setText(1, QString::number((cpsr_val >> 30) & 1)); // Z - Zero - cpsr->child(14)->setText(1, QString::number((cpsr_val >> 31) & 1)); // N - Negative/Less than -} - -void RegistersWidget::CreateVFPSystemRegisterChildren() { - QTreeWidgetItem* const fpscr = new QTreeWidgetItem(QStringList("FPSCR")); - fpscr->addChild(new QTreeWidgetItem(QStringList("IOC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("DZC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("OFC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("UFC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("IXC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("IDC"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("IOE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("DZE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("OFE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("UFE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("IXE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("IDE"))); - fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Length")))); - fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Stride")))); - fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Rounding Mode")))); - fpscr->addChild(new QTreeWidgetItem(QStringList("FZ"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("DN"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("V"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("C"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("Z"))); - fpscr->addChild(new QTreeWidgetItem(QStringList("N"))); - - QTreeWidgetItem* const fpexc = new QTreeWidgetItem(QStringList("FPEXC")); - fpexc->addChild(new QTreeWidgetItem(QStringList("IOC"))); - fpexc->addChild(new QTreeWidgetItem(QStringList("OFC"))); - fpexc->addChild(new QTreeWidgetItem(QStringList("UFC"))); - fpexc->addChild(new QTreeWidgetItem(QStringList("INV"))); - fpexc->addChild(new QTreeWidgetItem(QStringList(tr("Vector Iteration Count")))); - fpexc->addChild(new QTreeWidgetItem(QStringList("FP2V"))); - fpexc->addChild(new QTreeWidgetItem(QStringList("EN"))); - fpexc->addChild(new QTreeWidgetItem(QStringList("EX"))); - - vfp_system_registers->addChild(fpscr); - vfp_system_registers->addChild(fpexc); - vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST"))); - vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST2"))); -} - -void RegistersWidget::UpdateVFPSystemRegisterValues() { - UNIMPLEMENTED(); -} diff --git a/src/yuzu/debugger/registers.h b/src/yuzu/debugger/registers.h deleted file mode 100644 index 55bda5b59..000000000 --- a/src/yuzu/debugger/registers.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QDockWidget> -#include "ui_registers.h" - -class QTreeWidget; -class QTreeWidgetItem; -class EmuThread; - -class RegistersWidget : public QDockWidget { - Q_OBJECT - -public: - explicit RegistersWidget(QWidget* parent = nullptr); - -public slots: - void OnDebugModeEntered(); - void OnDebugModeLeft(); - - void OnEmulationStarting(EmuThread* emu_thread); - void OnEmulationStopping(); - -private: - void CreateCPSRChildren(); - void UpdateCPSRValues(); - - void CreateVFPSystemRegisterChildren(); - void UpdateVFPSystemRegisterValues(); - - Ui::ARMRegisters cpu_regs_ui; - - QTreeWidget* tree; - - QTreeWidgetItem* core_registers; - QTreeWidgetItem* vfp_registers; - QTreeWidgetItem* vfp_system_registers; - QTreeWidgetItem* cpsr; -}; diff --git a/src/yuzu/debugger/registers.ui b/src/yuzu/debugger/registers.ui deleted file mode 100644 index c81ae03f9..000000000 --- a/src/yuzu/debugger/registers.ui +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ARMRegisters</class> - <widget class="QDockWidget" name="ARMRegisters"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>ARM Registers</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeWidget" name="treeWidget"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <column> - <property name="text"> - <string>Register</string> - </property> - </column> - <column> - <property name="text"> - <string>Value</string> - </property> - </column> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 7a62f57b5..017bef13c 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -6,8 +6,8 @@ #include "yuzu/util/util.h" #include "core/core.h" -#include "core/hle/kernel/condition_variable.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -51,13 +51,21 @@ std::size_t WaitTreeItem::Row() const { } std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { - const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList(); std::vector<std::unique_ptr<WaitTreeThread>> item_list; - item_list.reserve(threads.size()); - for (std::size_t i = 0; i < threads.size(); ++i) { - item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); - item_list.back()->row = i; - } + std::size_t row = 0; + auto add_threads = [&](const std::vector<Kernel::SharedPtr<Kernel::Thread>>& threads) { + for (std::size_t i = 0; i < threads.size(); ++i) { + item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); + item_list.back()->row = row; + ++row; + } + }; + + add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList()); + add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList()); + add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList()); + add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList()); + return item_list; } @@ -67,6 +75,53 @@ QString WaitTreeText::GetText() const { return text; } +WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) { + mutex_value = Memory::Read32(mutex_address); + owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask); + owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle); +} + +QString WaitTreeMutexInfo::GetText() const { + return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0')); +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list; + + bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0; + + list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters))); + list.push_back(std::make_unique<WaitTreeText>( + tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0')))); + if (owner != nullptr) + list.push_back(std::make_unique<WaitTreeThread>(*owner)); + return list; +} + +WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {} + +QString WaitTreeCallstack::GetText() const { + return tr("Call stack"); +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list; + + constexpr size_t BaseRegister = 29; + u64 base_pointer = thread.context.cpu_registers[BaseRegister]; + + while (base_pointer != 0) { + u64 lr = Memory::Read64(base_pointer + sizeof(u64)); + if (lr == 0) + break; + list.push_back( + std::make_unique<WaitTreeText>(tr("0x%1").arg(lr - sizeof(u32), 16, 16, QChar('0')))); + base_pointer = Memory::Read64(base_pointer); + } + + return list; +} + WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {} bool WaitTreeExpandableItem::IsExpandable() const { @@ -84,11 +139,6 @@ std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitO switch (object.GetHandleType()) { case Kernel::HandleType::Event: return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object)); - case Kernel::HandleType::Mutex: - return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object)); - case Kernel::HandleType::ConditionVariable: - return std::make_unique<WaitTreeConditionVariable>( - static_cast<const Kernel::ConditionVariable&>(object)); case Kernel::HandleType::Timer: return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object)); case Kernel::HandleType::Thread: @@ -150,8 +200,8 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_READY: status = tr("ready"); break; - case THREADSTATUS_WAIT_ARB: - status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0')); + case THREADSTATUS_WAIT_HLE_EVENT: + status = tr("waiting for HLE return"); break; case THREADSTATUS_WAIT_SLEEP: status = tr("sleeping"); @@ -160,6 +210,9 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_WAIT_SYNCH_ANY: status = tr("waiting for objects"); break; + case THREADSTATUS_WAIT_MUTEX: + status = tr("waiting for mutex"); + break; case THREADSTATUS_DORMANT: status = tr("dormant"); break; @@ -180,12 +233,13 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkGreen); case THREADSTATUS_READY: return QColor(Qt::GlobalColor::darkBlue); - case THREADSTATUS_WAIT_ARB: + case THREADSTATUS_WAIT_HLE_EVENT: return QColor(Qt::GlobalColor::darkRed); case THREADSTATUS_WAIT_SLEEP: return QColor(Qt::GlobalColor::darkYellow); case THREADSTATUS_WAIT_SYNCH_ALL: case THREADSTATUS_WAIT_SYNCH_ANY: + case THREADSTATUS_WAIT_MUTEX: return QColor(Qt::GlobalColor::red); case THREADSTATUS_DORMANT: return QColor(Qt::GlobalColor::darkCyan); @@ -218,6 +272,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { } list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); + list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core))); + list.push_back( + std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask))); list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") .arg(thread.current_priority) @@ -225,17 +282,19 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>( tr("last running ticks = %1").arg(thread.last_running_ticks))); - if (thread.held_mutexes.empty()) { - list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex"))); - } else { - list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes)); - } + if (thread.mutex_wait_address != 0) + list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address)); + else + list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); + if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY || thread.status == THREADSTATUS_WAIT_SYNCH_ALL) { list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.IsSleepingOnWaitAll())); } + list.push_back(std::make_unique<WaitTreeCallstack>(thread)); + return list; } @@ -250,33 +309,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const { return list; } -WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {} - -std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const { - std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren()); - - const auto& mutex = static_cast<const Kernel::Mutex&>(object); - if (mutex.GetHasWaiters()) { - list.push_back(std::make_unique<WaitTreeText>(tr("locked by thread:"))); - list.push_back(std::make_unique<WaitTreeThread>(*mutex.GetHoldingThread())); - } else { - list.push_back(std::make_unique<WaitTreeText>(tr("free"))); - } - return list; -} - -WaitTreeConditionVariable::WaitTreeConditionVariable(const Kernel::ConditionVariable& object) - : WaitTreeWaitObject(object) {} - -std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeConditionVariable::GetChildren() const { - std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren()); - - const auto& condition_variable = static_cast<const Kernel::ConditionVariable&>(object); - list.push_back(std::make_unique<WaitTreeText>( - tr("available count = %1").arg(condition_variable.GetAvailableCount()))); - return list; -} - WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {} std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const { @@ -293,21 +325,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const { return list; } -WaitTreeMutexList::WaitTreeMutexList( - const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list) - : mutex_list(list) {} - -QString WaitTreeMutexList::GetText() const { - return tr("holding mutexes"); -} - -std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const { - std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size()); - std::transform(mutex_list.begin(), mutex_list.end(), list.begin(), - [](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); }); - return list; -} - WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list) : thread_list(list) {} diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index e538174eb..10fc9e968 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -16,8 +16,6 @@ class EmuThread; namespace Kernel { class WaitObject; class Event; -class Mutex; -class ConditionVariable; class Thread; class Timer; } // namespace Kernel @@ -61,6 +59,31 @@ public: bool IsExpandable() const override; }; +class WaitTreeMutexInfo : public WaitTreeExpandableItem { + Q_OBJECT +public: + explicit WaitTreeMutexInfo(VAddr mutex_address); + QString GetText() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +private: + VAddr mutex_address; + u32 mutex_value; + Kernel::Handle owner_handle; + Kernel::SharedPtr<Kernel::Thread> owner; +}; + +class WaitTreeCallstack : public WaitTreeExpandableItem { + Q_OBJECT +public: + explicit WaitTreeCallstack(const Kernel::Thread& thread); + QString GetText() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +private: + const Kernel::Thread& thread; +}; + class WaitTreeWaitObject : public WaitTreeExpandableItem { Q_OBJECT public: @@ -104,20 +127,6 @@ public: std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; -class WaitTreeMutex : public WaitTreeWaitObject { - Q_OBJECT -public: - explicit WaitTreeMutex(const Kernel::Mutex& object); - std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; -}; - -class WaitTreeConditionVariable : public WaitTreeWaitObject { - Q_OBJECT -public: - explicit WaitTreeConditionVariable(const Kernel::ConditionVariable& object); - std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; -}; - class WaitTreeTimer : public WaitTreeWaitObject { Q_OBJECT public: @@ -125,19 +134,6 @@ public: std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; -class WaitTreeMutexList : public WaitTreeExpandableItem { - Q_OBJECT -public: - explicit WaitTreeMutexList( - const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list); - - QString GetText() const override; - std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; - -private: - const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list; -}; - class WaitTreeThreadList : public WaitTreeExpandableItem { Q_OBJECT public: diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 76ced4de4..bbd681eae 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -315,7 +315,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { - LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data()); + NGLOG_ERROR(Frontend, "Could not find game list folder at {}", + dir_path.toLocal8Bit().data()); search_field->setFilterResult(0, 0); return; } @@ -364,7 +365,7 @@ static bool HasSupportedFileExtension(const std::string& file_name) { void GameList::RefreshGameDirectory() { if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { - LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + NGLOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); search_field->clear(); PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 5802b9855..aa9028399 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -18,7 +18,6 @@ #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/microprofile.h" -#include "common/platform.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" @@ -26,12 +25,14 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" -#include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" #include "yuzu/game_list.h" #include "yuzu/hotkeys.h" @@ -42,6 +43,15 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif +#ifdef _WIN32 +extern "C" { +// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable +// graphics +__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the @@ -69,10 +79,16 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + + debug_context = Tegra::DebugContext::Construct(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); + default_theme_paths = QIcon::themeSearchPaths(); + UpdateUITheme(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -152,14 +168,15 @@ void GMainWindow::InitializeDebugWidgets() { debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif - registersWidget = new RegistersWidget(this); - addDockWidget(Qt::RightDockWidgetArea, registersWidget); - registersWidget->hide(); - debug_menu->addAction(registersWidget->toggleViewAction()); - connect(this, &GMainWindow::EmulationStarting, registersWidget, - &RegistersWidget::OnEmulationStarting); - connect(this, &GMainWindow::EmulationStopping, registersWidget, - &RegistersWidget::OnEmulationStopping); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + + graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); + graphicsSurfaceWidget->hide(); + debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); @@ -308,6 +325,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } +bool GMainWindow::SupportsRequiredGLExtensions() { + QStringList unsupported_ext; + + if (!GLAD_GL_ARB_program_interface_query) + unsupported_ext.append("ARB_program_interface_query"); + if (!GLAD_GL_ARB_separate_shader_objects) + unsupported_ext.append("ARB_separate_shader_objects"); + if (!GLAD_GL_ARB_shader_storage_buffer_object) + unsupported_ext.append("ARB_shader_storage_buffer_object"); + if (!GLAD_GL_ARB_vertex_attrib_binding) + unsupported_ext.append("ARB_vertex_attrib_binding"); + + for (const QString& ext : unsupported_ext) + NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + + return unsupported_ext.empty(); +} + bool GMainWindow::LoadROM(const QString& filename) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) @@ -323,23 +358,34 @@ bool GMainWindow::LoadROM(const QString& filename) { return false; } + if (!SupportsRequiredGLExtensions()) { + QMessageBox::critical( + this, tr("Error while initializing OpenGL Core!"), + tr("Your GPU may not support one or more required OpenGL extensions. Please " + "ensure you have the latest graphics driver. See the log for more details.")); + return false; + } + Core::System& system{Core::System::GetInstance()}; - const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; + system.SetGPUDebugContext(debug_context); - Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; if (result != Core::System::ResultStatus::Success) { switch (result) { case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", - filename.toStdString().c_str()); + NGLOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString()); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("The ROM format is not supported.")); break; - + case Core::System::ResultStatus::ErrorUnsupportedArch: + NGLOG_CRITICAL(Frontend, "Unsupported architecture detected!", filename.toStdString()); + QMessageBox::critical(this, tr("Error while loading ROM!"), + tr("The ROM uses currently unusable 32-bit architecture")); + break; case Core::System::ResultStatus::ErrorSystemMode: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); + NGLOG_CRITICAL(Frontend, "Failed to load ROM!"); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("Could not determine the system mode.")); break; @@ -351,9 +397,9 @@ bool GMainWindow::LoadROM(const QString& filename) { "yuzu. A real Switch is required.<br/><br/>" "For more information on dumping and decrypting games, please see the following " "wiki pages: <ul>" - "<li><a href='https://citra-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " + "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " "Cartridges</a></li>" - "<li><a href='https://citra-emu.org/wiki/dumping-installed-titles/'>Dumping " + "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping " "Installed Titles</a></li>" "</ul>")); break; @@ -384,11 +430,12 @@ bool GMainWindow::LoadROM(const QString& filename) { } return false; } + Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); return true; } void GMainWindow::BootGame(const QString& filename) { - LOG_INFO(Frontend, "yuzu starting..."); + NGLOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list if (!LoadROM(filename)) @@ -403,17 +450,12 @@ void GMainWindow::BootGame(const QString& filename) { connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues - connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget, - &RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget, &WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); - connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget, - &RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget, &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); // Update the GUI - registersWidget->OnDebugModeEntered(); if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } @@ -636,6 +678,7 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); + UpdateUITheme(); config->Save(); } } @@ -674,18 +717,18 @@ void GMainWindow::UpdateStatusBar() { void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) { QMessageBox::StandardButton answer; QString status_message; - const QString common_message = - tr("The game you are trying to load requires additional files from your 3DS to be dumped " - "before playing.<br/><br/>For more information on dumping these files, please see the " - "following wiki page: <a " - "href='https://citra-emu.org/wiki/" - "dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>Dumping System " - "Archives and the Shared Fonts from a 3DS Console</a>.<br/><br/>Would you like to quit " - "back to the game list? Continuing emulation may result in crashes, corrupted save " - "data, or other bugs."); + const QString common_message = tr( + "The game you are trying to load requires additional files from your Switch to be dumped " + "before playing.<br/><br/>For more information on dumping these files, please see the " + "following wiki page: <a " + "href='https://yuzu-emu.org/wiki/" + "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System " + "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit " + "back to the game list? Continuing emulation may result in crashes, corrupted save " + "data, or other bugs."); switch (result) { case Core::System::ResultStatus::ErrorSystemFiles: { - QString message = "Citra was unable to locate a 3DS system archive"; + QString message = "yuzu was unable to locate a Switch system archive"; if (!details.empty()) { message.append(tr(": %1. ").arg(details.c_str())); } else { @@ -700,7 +743,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det } case Core::System::ResultStatus::ErrorSharedFont: { - QString message = tr("Citra was unable to locate the 3DS shared fonts. "); + QString message = tr("yuzu was unable to locate the Switch shared fonts. "); message.append(common_message); answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); @@ -751,9 +794,11 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - UISettings::values.geometry = saveGeometry(); + if (ui.action_Fullscreen->isChecked()) { + UISettings::values.geometry = saveGeometry(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); + } UISettings::values.state = saveState(); - UISettings::values.renderwindow_geometry = render_window->saveGeometry(); #if MICROPROFILE_ENABLED UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); UISettings::values.microprofile_visible = microProfileDialog->isVisible(); @@ -816,6 +861,32 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +void GMainWindow::UpdateUITheme() { + QStringList theme_paths(default_theme_paths); + if (UISettings::values.theme != UISettings::themes[0].second && + !UISettings::values.theme.isEmpty()) { + QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + QFile f(theme_uri); + if (!f.exists()) { + NGLOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); + } else { + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + GMainWindow::setStyleSheet(ts.readAll()); + } + theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); + QIcon::setThemeName(":/icons/" + UISettings::values.theme); + } else { + qApp->setStyleSheet(""); + GMainWindow::setStyleSheet(""); + theme_paths.append(QStringList{":/icons/default"}); + QIcon::setThemeName(":/icons/default"); + } + QIcon::setThemeSearchPaths(theme_paths); + emit UpdateThemedIcons(); +} + #ifdef main #undef main #endif diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..3e29d5fc4 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -2,8 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#ifndef _CITRA_QT_MAIN_HXX_ -#define _CITRA_QT_MAIN_HXX_ +#pragma once #include <memory> #include <QMainWindow> @@ -15,17 +14,17 @@ class Config; class EmuThread; class GameList; class GImageInfo; -class GPUCommandStreamWidget; -class GPUCommandListWidget; class GraphicsBreakPointsWidget; -class GraphicsTracingWidget; -class GraphicsVertexShaderWidget; +class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; -class RegistersWidget; class WaitTreeWidget; +namespace Tegra { +class DebugContext; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -64,6 +63,9 @@ signals: */ void EmulationStopping(); + // Signal that tells widgets to update icons to use the current theme + void UpdateThemedIcons(); + private: void InitializeWidgets(); void InitializeDebugWidgets(); @@ -76,6 +78,7 @@ private: void ConnectWidgetEvents(); void ConnectMenuEvents(); + bool SupportsRequiredGLExtensions(); bool LoadROM(const QString& filename); void BootGame(const QString& filename); void ShutdownGame(); @@ -138,6 +141,8 @@ private: Ui::MainWindow ui; + std::shared_ptr<Tegra::DebugContext> debug_context; + GRenderWindow* render_window; GameList* game_list; @@ -150,22 +155,24 @@ private: std::unique_ptr<Config> config; - // Whether emulation is currently running in Citra. + // Whether emulation is currently running in yuzu. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; - RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + // stores default icon theme search paths for the platform + QStringList default_theme_paths; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; }; - -#endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 9036ce2c1..8e215a002 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,6 +15,10 @@ namespace UISettings { using ContextualShortcut = std::pair<QString, int>; using Shortcut = std::pair<QString, ContextualShortcut>; +static const std::array<std::pair<QString, QString>, 2> themes = { + {std::make_pair(QString("Default"), QString("default")), + std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; + struct Values { QByteArray geometry; QByteArray state; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index bf79d2e81..ee6e4d658 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -27,17 +27,17 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { const char* location = this->sdl2_config_loc.c_str(); if (sdl2_config->ParseError() < 0) { if (retry) { - LOG_WARNING(Config, "Failed to load %s. Creating file from defaults...", location); + NGLOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); FileUtil::CreateFullPath(location); FileUtil::WriteStringToFile(true, default_contents, location); sdl2_config = std::make_unique<INIReader>(location); // Reopen file return LoadINI(default_contents, false); } - LOG_ERROR(Config, "Failed."); + NGLOG_ERROR(Config, "Failed."); return false; } - LOG_INFO(Config, "Successfully loaded %s", location); + NGLOG_INFO(Config, "Successfully loaded {}", location); return true; } @@ -90,8 +90,8 @@ void Config::ReadValues() { sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); // Core - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 0)); + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); + Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); // Renderer Settings::values.resolution_factor = @@ -107,6 +107,9 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + // System + Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true); + // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 156f7a1a4..1c438c3f5 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -8,7 +8,7 @@ namespace DefaultINI { const char* sdl2_config_file = R"( [Controls] -# The input devices and parameters for each 3DS native input +# The input devices and parameters for each Switch native input # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values @@ -76,9 +76,13 @@ motion_device= touch_device= [Core] -# Which CPU core to use for CPU emulation -# 0 (default): Unicorn (slow), 1: Dynarmic (faster) -cpu_core = +# Whether to use the Just-In-Time (JIT) compiler for CPU emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_cpu_jit = + +# Whether to use multi-core for CPU emulation +# 0 (default): Disabled, 1: Enabled +use_multi_core= [Renderer] # Whether to use software or hardware rendering. @@ -154,9 +158,9 @@ output_device = use_virtual_sd = [System] -# The system model that Citra will try to emulate -# 0: Old 3DS (default), 1: New 3DS -is_new_3ds = +# Whether the system is docked +# 1 (default): Yes, 0: No +use_docked_mode = # The system region that Citra will use during emulation # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan @@ -177,12 +181,12 @@ gdbstub_port=24689 # 0: No, 1 (default): Yes enable_telemetry = # Endpoint URL for submitting telemetry data -telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry +telemetry_endpoint_url = # Endpoint URL to verify the username and token -verify_endpoint_url = https://services.citra-emu.org/api/profile -# Username and token for Citra Web Service +verify_endpoint_url = +# Username and token for yuzu Web Service # See https://services.citra-emu.org/ for more info -citra_username = -citra_token = +yuzu_username = +yuzu_token = )"; } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 3d7cd06a4..cfd8eb7e6 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -7,6 +7,7 @@ #include <string> #define SDL_MAIN_HANDLED #include <SDL.h> +#include <fmt/format.h> #include <glad/glad.h> #include "common/logging/log.h" #include "common/scm_rev.h" @@ -56,14 +57,53 @@ void EmuWindow_SDL2::OnResize() { UpdateCurrentFramebufferLayout(width, height); } -EmuWindow_SDL2::EmuWindow_SDL2() { +void EmuWindow_SDL2::Fullscreen() { + if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { + return; + } + + NGLOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError()); + + // Try a different fullscreening method + NGLOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); + if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { + return; + } + + NGLOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError()); + + // Fallback algorithm: Maximise window. + // Works on all systems (unless something is seriously wrong), so no fallback for this one. + NGLOG_INFO(Frontend, "Falling back on a maximised window..."); + SDL_MaximizeWindow(render_window); +} + +bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { + std::vector<std::string> unsupported_ext; + + if (!GLAD_GL_ARB_program_interface_query) + unsupported_ext.push_back("ARB_program_interface_query"); + if (!GLAD_GL_ARB_separate_shader_objects) + unsupported_ext.push_back("ARB_separate_shader_objects"); + if (!GLAD_GL_ARB_shader_storage_buffer_object) + unsupported_ext.push_back("ARB_shader_storage_buffer_object"); + if (!GLAD_GL_ARB_vertex_attrib_binding) + unsupported_ext.push_back("ARB_vertex_attrib_binding"); + + for (const std::string& ext : unsupported_ext) + NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext); + + return unsupported_ext.empty(); +} + +EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { InputCommon::Init(); SDL_SetMainReady(); // Initialize the window if (SDL_Init(SDL_INIT_VIDEO) < 0) { - LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); + NGLOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); } @@ -76,8 +116,8 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - std::string window_title = Common::StringFromFormat("yuzu %s| %s-%s ", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); render_window = SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, // x position @@ -86,19 +126,28 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); if (render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting..."); + NGLOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting..."); exit(1); } + if (fullscreen) { + Fullscreen(); + } + gl_context = SDL_GL_CreateContext(render_window); if (gl_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting..."); + NGLOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting..."); exit(1); } if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { - LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting..."); + NGLOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting..."); + exit(1); + } + + if (!SupportsRequiredGLExtensions()) { + NGLOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting..."); exit(1); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 3664d2fbe..1d835c3c6 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -12,7 +12,7 @@ struct SDL_Window; class EmuWindow_SDL2 : public EmuWindow { public: - EmuWindow_SDL2(); + explicit EmuWindow_SDL2(bool fullscreen); ~EmuWindow_SDL2(); /// Swap buffers to display the next frame @@ -43,6 +43,12 @@ private: /// Called by PollEvents when any event that may cause the window to be resized occurs void OnResize(); + /// Called when user passes the fullscreen parameter flag + void Fullscreen(); + + /// Whether the GPU and driver supports the OpenGL extension required + bool SupportsRequiredGLExtensions(); + /// Called when a configuration change affects the minimal size of the window void OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) override; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 8f7c75796..95e568b7b 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -7,8 +7,19 @@ #include <string> #include <thread> -// This needs to be included before getopt.h because the latter #defines symbols used by it +#include "common/logging/backend.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" #include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/gdbstub/gdbstub.h" +#include "core/loader/loader.h" +#include "core/settings.h" +#include "yuzu_cmd/config.h" +#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #ifdef _MSC_VER #include <getopt.h> @@ -24,23 +35,20 @@ #include <shellapi.h> #endif -#include "common/logging/backend.h" -#include "common/logging/filter.h" -#include "common/logging/log.h" -#include "common/scm_rev.h" -#include "common/scope_exit.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/gdbstub/gdbstub.h" -#include "core/loader/loader.h" -#include "core/settings.h" -#include "yuzu_cmd/config.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#ifdef _WIN32 +extern "C" { +// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable +// graphics +__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] <filename>\n" "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" + "-f, --fullscreen Start in fullscreen mode\n" "-h, --help Display this help and exit\n" "-v, --version Output version information and exit\n"; } @@ -61,21 +69,24 @@ int main(int argc, char** argv) { auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); if (argv_w == nullptr) { - LOG_CRITICAL(Frontend, "Failed to get command line arguments"); + NGLOG_CRITICAL(Frontend, "Failed to get command line arguments"); return -1; } #endif std::string filepath; + bool fullscreen = false; + static struct option long_options[] = { {"gdbport", required_argument, 0, 'g'}, + {"fullscreen", no_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0}, }; while (optind < argc) { - char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index); + char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index); if (arg != -1) { switch (arg) { case 'g': @@ -89,6 +100,10 @@ int main(int argc, char** argv) { exit(1); } break; + case 'f': + fullscreen = true; + NGLOG_INFO(Frontend, "Starting in fullscreen mode..."); + break; case 'h': PrintHelp(argv[0]); return 0; @@ -117,7 +132,7 @@ int main(int argc, char** argv) { SCOPE_EXIT({ MicroProfileShutdown(); }); if (filepath.empty()) { - LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); + NGLOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); return -1; } @@ -128,7 +143,7 @@ int main(int argc, char** argv) { Settings::values.use_gdbstub = use_gdbstub; Settings::Apply(); - std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>()}; + std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)}; Core::System& system{Core::System::GetInstance()}; @@ -138,28 +153,28 @@ int main(int argc, char** argv) { switch (load_result) { case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str()); + NGLOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str()); return -1; case Core::System::ResultStatus::ErrorLoader: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); + NGLOG_CRITICAL(Frontend, "Failed to load ROM!"); return -1; case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: - LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " - "being used with yuzu. \n\n For more information on dumping and " - "decrypting games, please refer to: " - "https://citra-emu.org/wiki/dumping-game-cartridges/"); + NGLOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " + "being used with yuzu. \n\n For more information on dumping and " + "decrypting games, please refer to: " + "https://yuzu-emu.org/wiki/dumping-game-cartridges/"); return -1; case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: - LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); + NGLOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); return -1; case Core::System::ResultStatus::ErrorNotInitialized: - LOG_CRITICAL(Frontend, "CPUCore not initialized"); + NGLOG_CRITICAL(Frontend, "CPUCore not initialized"); return -1; case Core::System::ResultStatus::ErrorSystemMode: - LOG_CRITICAL(Frontend, "Failed to determine system mode!"); + NGLOG_CRITICAL(Frontend, "Failed to determine system mode!"); return -1; case Core::System::ResultStatus::ErrorVideoCore: - LOG_CRITICAL(Frontend, "VideoCore not initialized"); + NGLOG_CRITICAL(Frontend, "VideoCore not initialized"); return -1; case Core::System::ResultStatus::Success: break; // Expected case diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc index 7cb8a14e1..7de8ef3d9 100644 --- a/src/yuzu_cmd/yuzu.rc +++ b/src/yuzu_cmd/yuzu.rc @@ -6,7 +6,7 @@ // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -CITRA_ICON ICON "../../dist/yuzu.ico" +YUZU_ICON ICON "../../dist/yuzu.ico" ///////////////////////////////////////////////////////////////////////////// |