diff options
167 files changed, 3609 insertions, 2681 deletions
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index 9b451d3ab..5070b92d1 100755 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -18,7 +18,8 @@ cmake .. \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_QT_TRANSLATION=ON \ -DUSE_DISCORD_PRESENCE=ON \ - -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} + -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ + -DYUZU_USE_BUNDLED_FFMPEG=ON make -j$(nproc) diff --git a/CMakeLists.txt b/CMakeLists.txt index 857550e71..0e064ab44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF) option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF) -CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) +option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" "${WIN32}") option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) @@ -376,7 +376,7 @@ if (ENABLE_SDL2) if (YUZU_USE_BUNDLED_SDL2) # Detect toolchain and platform if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64) - set(SDL2_VER "SDL2-2.0.15-prerelease") + set(SDL2_VER "SDL2-2.0.16") else() message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.") endif() @@ -396,7 +396,7 @@ if (ENABLE_SDL2) elseif (YUZU_USE_EXTERNAL_SDL2) message(STATUS "Using SDL2 from externals.") else() - find_package(SDL2 2.0.15 REQUIRED) + find_package(SDL2 2.0.16 REQUIRED) # Some installations don't set SDL2_LIBRARIES if("${SDL2_LIBRARIES}" STREQUAL "") @@ -496,7 +496,7 @@ endif() # Ensure libusb is properly configured (based on dolphin libusb include) if(NOT APPLE AND NOT YUZU_USE_BUNDLED_LIBUSB) include(FindPkgConfig) - if (PKG_CONFIG_FOUND) + if (PKG_CONFIG_FOUND AND NOT CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD") pkg_check_modules(LIBUSB QUIET libusb-1.0>=1.0.24) else() find_package(LibUSB) @@ -583,8 +583,32 @@ if (YUZU_USE_BUNDLED_FFMPEG) "${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}" CACHE PATH "Path to FFmpeg headers" FORCE) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + Include(FindPkgConfig REQUIRED) + pkg_check_modules(LIBVA libva) + endif() + if(LIBVA_FOUND) + pkg_check_modules(LIBDRM libdrm REQUIRED) + find_package(X11 REQUIRED) + pkg_check_modules(LIBVA-DRM libva-drm REQUIRED) + pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED) + set(FFmpeg_LIBVA_LIBRARIES + ${LIBDRM_LIBRARIES} + ${X11_LIBRARIES} + ${LIBVA-DRM_LIBRARIES} + ${LIBVA-X11_LIBRARIES} + ${LIBVA_LIBRARIES}) + set(FFmpeg_HWACCEL_FLAGS + --enable-hwaccel=h264_vaapi + --enable-hwaccel=vp9_vaapi + --enable-libdrm) + message(STATUS "VA-API found") + else() + set(FFmpeg_HWACCEL_FLAGS --disable-vaapi) + endif() + # `configure` parameters builds only exactly what yuzu needs from FFmpeg - # `--disable-{vaapi,vdpau}` is needed to avoid linking issues + # `--disable-vdpau` is needed to avoid linking issues add_custom_command( OUTPUT ${FFmpeg_MAKEFILE} @@ -600,13 +624,16 @@ if (YUZU_USE_BUNDLED_FFMPEG) --disable-network --disable-postproc --disable-swresample - --disable-vaapi --disable-vdpau --enable-decoder=h264 --enable-decoder=vp9 + --cc="${CMAKE_C_COMPILER}" + --cxx="${CMAKE_CXX_COMPILER}" + ${FFmpeg_HWACCEL_FLAGS} WORKING_DIRECTORY ${FFmpeg_BUILD_DIR} ) + unset(FFmpeg_HWACCEL_FLAGS) # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child # with context of the jobserver. Also helps ninja users. @@ -616,9 +643,10 @@ if (YUZU_USE_BUNDLED_FFMPEG) OUTPUT_VARIABLE SYSTEM_THREADS) + set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES}) add_custom_command( OUTPUT - ${FFmpeg_LIBRARIES} + ${FFmpeg_BUILD_LIBRARIES} COMMAND make -j${SYSTEM_THREADS} WORKING_DIRECTORY @@ -628,7 +656,12 @@ if (YUZU_USE_BUNDLED_FFMPEG) # ALL makes this custom target build every time # but it won't actually build if the DEPENDS parameter is up to date add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE}) - add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_LIBRARIES} ffmpeg-configure) + add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_BUILD_LIBRARIES} ffmpeg-configure) + link_libraries(${FFmpeg_LIBVA_LIBRARIES}) + set(FFmpeg_LIBRARIES ${FFmpeg_LIBVA_LIBRARIES} ${FFmpeg_BUILD_LIBRARIES} + CACHE PATH "Paths to FFmpeg libraries" FORCE) + unset(FFmpeg_BUILD_LIBRARIES) + unset(FFmpeg_LIBVA_LIBRARIES) if (FFmpeg_FOUND) message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}") @@ -668,7 +701,7 @@ if (APPLE) elseif (WIN32) # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) - set(PLATFORM_LIBRARIES winmm ws2_32) + set(PLATFORM_LIBRARIES winmm ws2_32 iphlpapi) if (MINGW) # PSAPI is the Process Status API set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 9915a40ba..f0908a7f1 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -51,11 +51,11 @@ QPushButton#GPUStatusBarButton:hover { } QPushButton#GPUStatusBarButton:checked { - color: #ff8040; + color: #b06020; } QPushButton#GPUStatusBarButton:!checked { - color: #40dd40; + color: #109010; } QPushButton#buttonRefreshDevices { diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4b8d35548..0c2c059a9 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -7,7 +7,9 @@ include(DownloadExternals) # xbyak if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) add_library(xbyak INTERFACE) - target_include_directories(xbyak SYSTEM INTERFACE ./xbyak/xbyak) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) + target_include_directories(xbyak SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES) endif() @@ -19,6 +21,7 @@ target_include_directories(catch-single-include INTERFACE catch/single_include) if (ARCHITECTURE_x86_64) set(DYNARMIC_TESTS OFF) set(DYNARMIC_NO_BUNDLED_FMT ON) + set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) add_subdirectory(dynarmic) endif() diff --git a/externals/SDL b/externals/SDL -Subproject 2f248a2a31c3323ecc37c00ad5e269e347ae392 +Subproject 25f9ed87ff6947d9576fc9d79dee0784e638ac5 diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 7946868af49d403fe54c92d2d60ef986513d1fe +Subproject 517e35f845e010788b6febe42fd6ddb187b8c23 diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 151ddc462..12bdc097a 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -67,6 +67,8 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE) "${LIBUSB_MAKEFILE}" COMMAND env + CC="${CMAKE_C_COMPILER}" + CXX="${CMAKE_CXX_COMPILER}" CFLAGS="${LIBUSB_CFLAGS}" sh "${LIBUSB_CONFIGURE}" ${LIBUSB_CONFIGURE_ARGS} diff --git a/src/common/assert.h b/src/common/assert.h index b3ba35c0f..33060d865 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -52,8 +52,12 @@ assert_noinline_call(const Fn& fn) { #define DEBUG_ASSERT(_a_) ASSERT(_a_) #define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) #else // not debug -#define DEBUG_ASSERT(_a_) -#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) +#define DEBUG_ASSERT(_a_) \ + do { \ + } while (0) +#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \ + do { \ + } while (0) #endif #define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!") diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp index 357cf5855..9f8671982 100644 --- a/src/common/fs/fs_util.cpp +++ b/src/common/fs/fs_util.cpp @@ -20,6 +20,10 @@ std::string ToUTF8String(std::u8string_view u8_string) { return std::string{u8_string.begin(), u8_string.end()}; } +std::string BufferToUTF8String(std::span<const u8> buffer) { + return std::string{buffer.begin(), std::ranges::find(buffer, u8{0})}; +} + std::string PathToUTF8String(const std::filesystem::path& path) { return ToUTF8String(path.u8string()); } diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h index ec9950ee7..1ec82eb35 100644 --- a/src/common/fs/fs_util.h +++ b/src/common/fs/fs_util.h @@ -47,6 +47,17 @@ concept IsChar = std::same_as<T, char>; [[nodiscard]] std::string ToUTF8String(std::u8string_view u8_string); /** + * Converts a buffer of bytes to a UTF8-encoded std::string. + * This converts from the start of the buffer until the first encountered null-terminator. + * If no null-terminator is found, this converts the entire buffer instead. + * + * @param buffer Buffer of bytes + * + * @returns UTF-8 encoded std::string. + */ +[[nodiscard]] std::string BufferToUTF8String(std::span<const u8> buffer); + +/** * Converts a filesystem path to a UTF-8 encoded std::string. * * @param path Filesystem path diff --git a/src/common/hex_util.h b/src/common/hex_util.h index f5f9e4507..5e9b6ef8b 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -61,7 +61,7 @@ template <typename ContiguousContainer> return out; } -[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) { +[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[33]) { return HexStringToArray<16>(data); } diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 2a5a7596c..6661244cf 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -6,7 +6,7 @@ #include <windows.h> #include "common/dynamic_library.h" -#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv +#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -343,7 +343,7 @@ private: std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset }; -#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv +#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv class HostMemory::Impl { public: @@ -357,7 +357,12 @@ public: }); // Backing memory initialization +#if defined(__FreeBSD__) && __FreeBSD__ < 13 + // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 + fd = shm_open(SHM_ANON, O_RDWR, 0600); +#else fd = memfd_create("HostMemory", 0); +#endif if (fd == -1) { LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno)); throw std::bad_alloc{}; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 61dddab3f..13edda9c9 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -2,13 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> #include <atomic> #include <chrono> #include <climits> -#include <condition_variable> -#include <memory> -#include <mutex> #include <thread> #include <vector> @@ -16,104 +12,229 @@ #include <windows.h> // For OutputDebugStringW #endif -#include "common/assert.h" #include "common/fs/file.h" #include "common/fs/fs.h" +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" #include "common/literals.h" #include "common/logging/backend.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/settings.h" +#ifdef _WIN32 #include "common/string_util.h" +#endif #include "common/threadsafe_queue.h" namespace Common::Log { +namespace { + /** - * Static state as a singleton. + * Interface for logging backends. */ -class Impl { +class Backend { public: - static Impl& Instance() { - static Impl backend; - return backend; + virtual ~Backend() = default; + + virtual void Write(const Entry& entry) = 0; + + virtual void EnableForStacktrace() = 0; + + virtual void Flush() = 0; +}; + +/** + * Backend that writes to stderr and with color + */ +class ColorConsoleBackend final : public Backend { +public: + explicit ColorConsoleBackend() = default; + + ~ColorConsoleBackend() override = default; + + void Write(const Entry& entry) override { + if (enabled.load(std::memory_order_relaxed)) { + PrintColoredMessage(entry); + } } - Impl(const Impl&) = delete; - Impl& operator=(const Impl&) = delete; + void Flush() override { + // stderr shouldn't be buffered + } - Impl(Impl&&) = delete; - Impl& operator=(Impl&&) = delete; + void EnableForStacktrace() override { + enabled = true; + } - void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, std::string message) { - message_queue.Push( - CreateEntry(log_class, log_level, filename, line_num, function, std::move(message))); + void SetEnabled(bool enabled_) { + enabled = enabled_; + } + +private: + std::atomic_bool enabled{false}; +}; + +/** + * Backend that writes to a file passed into the constructor + */ +class FileBackend final : public Backend { +public: + explicit FileBackend(const std::filesystem::path& filename) { + auto old_filename = filename; + old_filename += ".old.txt"; + + // Existence checks are done within the functions themselves. + // We don't particularly care if these succeed or not. + static_cast<void>(FS::RemoveFile(old_filename)); + static_cast<void>(FS::RenameFile(filename, old_filename)); + + file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, + FS::FileType::TextFile); + } + + ~FileBackend() override = default; + + void Write(const Entry& entry) override { + if (!enabled) { + return; + } + + bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); + + using namespace Common::Literals; + // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. + const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB; + const bool write_limit_exceeded = bytes_written > write_limit; + if (entry.log_level >= Level::Error || write_limit_exceeded) { + if (write_limit_exceeded) { + // Stop writing after the write limit is exceeded. + // Don't close the file so we can print a stacktrace if necessary + enabled = false; + } + file->Flush(); + } + } + + void Flush() override { + file->Flush(); + } + + void EnableForStacktrace() override { + enabled = true; + bytes_written = 0; } - void AddBackend(std::unique_ptr<Backend> backend) { - std::lock_guard lock{writing_mutex}; - backends.push_back(std::move(backend)); +private: + std::unique_ptr<FS::IOFile> file; + bool enabled = true; + std::size_t bytes_written = 0; +}; + +/** + * Backend that writes to Visual Studio's output window + */ +class DebuggerBackend final : public Backend { +public: + explicit DebuggerBackend() = default; + + ~DebuggerBackend() override = default; + + void Write(const Entry& entry) override { +#ifdef _WIN32 + ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); +#endif } - void RemoveBackend(std::string_view backend_name) { - std::lock_guard lock{writing_mutex}; + void Flush() override {} + + void EnableForStacktrace() override {} +}; + +bool initialization_in_progress_suppress_logging = false; - std::erase_if(backends, [&backend_name](const auto& backend) { - return backend_name == backend->GetName(); - }); +/** + * Static state as a singleton. + */ +class Impl { +public: + static Impl& Instance() { + if (!instance) { + abort(); + } + return *instance; } - const Filter& GetGlobalFilter() const { - return filter; + static void Initialize() { + if (instance) { + abort(); + } + using namespace Common::FS; + initialization_in_progress_suppress_logging = true; + const auto& log_dir = GetYuzuPath(YuzuPath::LogDir); + void(CreateDir(log_dir)); + Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter), + Deleter); + initialization_in_progress_suppress_logging = false; } + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + void SetGlobalFilter(const Filter& f) { filter = f; } - Backend* GetBackend(std::string_view backend_name) { - const auto it = - std::find_if(backends.begin(), backends.end(), - [&backend_name](const auto& i) { return backend_name == i->GetName(); }); - if (it == backends.end()) - return nullptr; - return it->get(); + void SetColorConsoleBackendEnabled(bool enabled) { + color_console_backend.SetEnabled(enabled); + } + + void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, + const char* function, std::string message) { + if (!filter.CheckMessage(log_class, log_level)) + return; + const Entry& entry = + CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)); + message_queue.Push(entry); } private: - Impl() { - backend_thread = std::thread([&] { - Entry entry; - auto write_logs = [&](Entry& e) { - std::lock_guard lock{writing_mutex}; - for (const auto& backend : backends) { - backend->Write(e); - } - }; - while (true) { - entry = message_queue.PopWait(); - if (entry.final_entry) { - break; - } - write_logs(entry); - } + Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) + : filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] { + Common::SetCurrentThreadName("yuzu:Log"); + Entry entry; + const auto write_logs = [this, &entry]() { + ForEachBackend([&entry](Backend& backend) { backend.Write(entry); }); + }; + while (true) { + entry = message_queue.PopWait(); + if (entry.final_entry) { + break; + } + write_logs(); + } + // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a + // case where a system is repeatedly spamming logs even on close. + int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100; + while (max_logs_to_write-- && message_queue.Pop(entry)) { + write_logs(); + } + })} {} - // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a - // case where a system is repeatedly spamming logs even on close. - const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100; - int logs_written = 0; - while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { - write_logs(entry); - } - }); + ~Impl() { + StopBackendThread(); } - ~Impl() { - Entry entry; - entry.final_entry = true; - message_queue.Push(entry); + void StopBackendThread() { + Entry stop_entry{}; + stop_entry.final_entry = true; + message_queue.Push(stop_entry); backend_thread.join(); } @@ -135,100 +256,51 @@ private: }; } - std::mutex writing_mutex; - std::thread backend_thread; - std::vector<std::unique_ptr<Backend>> backends; - MPSCQueue<Entry> message_queue; - Filter filter; - std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; -}; - -ConsoleBackend::~ConsoleBackend() = default; - -void ConsoleBackend::Write(const Entry& entry) { - PrintMessage(entry); -} - -ColorConsoleBackend::~ColorConsoleBackend() = default; - -void ColorConsoleBackend::Write(const Entry& entry) { - PrintColoredMessage(entry); -} - -FileBackend::FileBackend(const std::filesystem::path& filename) { - auto old_filename = filename; - old_filename += ".old.txt"; - - // Existence checks are done within the functions themselves. - // We don't particularly care if these succeed or not. - FS::RemoveFile(old_filename); - void(FS::RenameFile(filename, old_filename)); - - file = - std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); -} - -FileBackend::~FileBackend() = default; + void ForEachBackend(auto lambda) { + lambda(static_cast<Backend&>(debugger_backend)); + lambda(static_cast<Backend&>(color_console_backend)); + lambda(static_cast<Backend&>(file_backend)); + } -void FileBackend::Write(const Entry& entry) { - if (!file->IsOpen()) { - return; + static void Deleter(Impl* ptr) { + delete ptr; } - using namespace Common::Literals; - // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. - constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB; - constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB; + static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter}; - const bool write_limit_exceeded = - bytes_written > MAX_BYTES_WRITTEN_EXTENDED || - (bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging); + Filter filter; + DebuggerBackend debugger_backend{}; + ColorConsoleBackend color_console_backend{}; + FileBackend file_backend; - // Close the file after the write limit is exceeded. - if (write_limit_exceeded) { - file->Close(); - return; - } + std::thread backend_thread; + MPSCQueue<Entry> message_queue{}; + std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; +}; +} // namespace - bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); - if (entry.log_level >= Level::Error) { - file->Flush(); - } +void Initialize() { + Impl::Initialize(); } -DebuggerBackend::~DebuggerBackend() = default; - -void DebuggerBackend::Write(const Entry& entry) { -#ifdef _WIN32 - ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); -#endif +void DisableLoggingInTests() { + initialization_in_progress_suppress_logging = true; } void SetGlobalFilter(const Filter& filter) { Impl::Instance().SetGlobalFilter(filter); } -void AddBackend(std::unique_ptr<Backend> backend) { - Impl::Instance().AddBackend(std::move(backend)); -} - -void RemoveBackend(std::string_view backend_name) { - Impl::Instance().RemoveBackend(backend_name); -} - -Backend* GetBackend(std::string_view backend_name) { - return Impl::Instance().GetBackend(backend_name); +void SetColorConsoleBackendEnabled(bool enabled) { + Impl::Instance().SetColorConsoleBackendEnabled(enabled); } 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) { - auto& instance = Impl::Instance(); - const auto& filter = instance.GetGlobalFilter(); - if (!filter.CheckMessage(log_class, log_level)) - return; - - instance.PushEntry(log_class, log_level, filename, line_num, function, - fmt::vformat(format, args)); + if (!initialization_in_progress_suppress_logging) { + Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, + fmt::vformat(format, args)); + } } } // namespace Common::Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 4b9a910c1..cb7839ee9 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -5,120 +5,21 @@ #pragma once #include <filesystem> -#include <memory> -#include <string> -#include <string_view> #include "common/logging/filter.h" -#include "common/logging/log.h" - -namespace Common::FS { -class IOFile; -} namespace Common::Log { class Filter; -/** - * Interface for logging backends. As loggers can be created and removed at runtime, this can be - * used by a frontend for adding a custom logging backend as needed - */ -class Backend { -public: - virtual ~Backend() = default; - - virtual void SetFilter(const Filter& new_filter) { - filter = new_filter; - } - virtual const char* GetName() const = 0; - virtual void Write(const Entry& entry) = 0; - -private: - Filter filter; -}; - -/** - * Backend that writes to stderr without any color commands - */ -class ConsoleBackend : public Backend { -public: - ~ConsoleBackend() override; - - static const char* Name() { - return "console"; - } - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to stderr and with color - */ -class ColorConsoleBackend : public Backend { -public: - ~ColorConsoleBackend() override; - - static const char* Name() { - return "color_console"; - } - - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; +/// Initializes the logging system. This should be the first thing called in main. +void Initialize(); -/** - * Backend that writes to a file passed into the constructor - */ -class FileBackend : public Backend { -public: - explicit FileBackend(const std::filesystem::path& filename); - ~FileBackend() override; - - static const char* Name() { - return "file"; - } - - const char* GetName() const override { - return Name(); - } - - void Write(const Entry& entry) override; - -private: - std::unique_ptr<FS::IOFile> file; - std::size_t bytes_written = 0; -}; - -/** - * Backend that writes to Visual Studio's output window - */ -class DebuggerBackend : public Backend { -public: - ~DebuggerBackend() override; - - static const char* Name() { - return "debugger"; - } - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -void AddBackend(std::unique_ptr<Backend> backend); - -void RemoveBackend(std::string_view backend_name); - -Backend* GetBackend(std::string_view backend_name); +void DisableLoggingInTests(); /** - * The global filter will prevent any messages from even being processed if they are filtered. Each - * backend can have a filter, but if the level is lower than the global filter, the backend will - * never get the message + * The global filter will prevent any messages from even being processed if they are filtered. */ void SetGlobalFilter(const Filter& filter); -} // namespace Common::Log
\ No newline at end of file + +void SetColorConsoleBackendEnabled(bool enabled); +} // namespace Common::Log diff --git a/src/common/settings.h b/src/common/settings.h index d8730f515..20769d310 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -4,6 +4,7 @@ #pragma once +#include <algorithm> #include <array> #include <atomic> #include <chrono> @@ -42,6 +43,11 @@ enum class CPUAccuracy : u32 { Unsafe = 2, }; +enum class FullscreenMode : u32 { + Borderless = 0, + Exclusive = 1, +}; + /** The BasicSetting class is a simple resource manager. It defines a label and default value * alongside the actual value of the setting for simpler and less-error prone use with frontend * configurations. Setting a default value and label is required, though subclasses may deviate from @@ -69,14 +75,14 @@ public: */ explicit BasicSetting(const Type& default_val, const std::string& name) : default_value{default_val}, global{default_val}, label{name} {} - ~BasicSetting() = default; + virtual ~BasicSetting() = default; /** * Returns a reference to the setting's value. * * @returns A reference to the setting */ - [[nodiscard]] const Type& GetValue() const { + [[nodiscard]] virtual const Type& GetValue() const { return global; } @@ -85,7 +91,7 @@ public: * * @param value The desired value */ - void SetValue(const Type& value) { + virtual void SetValue(const Type& value) { Type temp{value}; std::swap(global, temp); } @@ -115,7 +121,7 @@ public: * * @returns A reference to the setting */ - const Type& operator=(const Type& value) { + virtual const Type& operator=(const Type& value) { Type temp{value}; std::swap(global, temp); return global; @@ -126,7 +132,7 @@ public: * * @returns A reference to the setting */ - explicit operator const Type&() const { + explicit virtual operator const Type&() const { return global; } @@ -137,6 +143,51 @@ protected: }; /** + * BasicRangedSetting class is intended for use with quantifiable settings that need a more + * restrictive range than implicitly defined by its type. Implements a minimum and maximum that is + * simply used to sanitize SetValue and the assignment overload. + */ +template <typename Type> +class BasicRangedSetting : virtual public BasicSetting<Type> { +public: + /** + * Sets a default value, minimum value, maximum value, and label. + * + * @param default_val Intial value of the setting, and default value of the setting + * @param min_val Sets the minimum allowed value of the setting + * @param max_val Sets the maximum allowed value of the setting + * @param name Label for the setting + */ + explicit BasicRangedSetting(const Type& default_val, const Type& min_val, const Type& max_val, + const std::string& name) + : BasicSetting<Type>{default_val, name}, minimum{min_val}, maximum{max_val} {} + virtual ~BasicRangedSetting() = default; + + /** + * Like BasicSetting's SetValue, except value is clamped to the range of the setting. + * + * @param value The desired value + */ + void SetValue(const Type& value) override { + this->global = std::clamp(value, minimum, maximum); + } + + /** + * Like BasicSetting's assignment overload, except value is clamped to the range of the setting. + * + * @param value The desired value + * @returns A reference to the setting's value + */ + const Type& operator=(const Type& value) override { + this->global = std::clamp(value, minimum, maximum); + return this->global; + } + + const Type minimum; ///< Minimum allowed value of the setting + const Type maximum; ///< Maximum allowed value of the setting +}; + +/** * The Setting class is a slightly more complex version of the BasicSetting class. This adds a * custom setting to switch to when a guest application specifically requires it. The effect is that * other components of the emulator can access the setting's intended value without any need for the @@ -147,7 +198,7 @@ protected: * Like the BasicSetting, this requires setting a default value and label to use. */ template <typename Type> -class Setting final : public BasicSetting<Type> { +class Setting : virtual public BasicSetting<Type> { public: /** * Sets a default value, label, and setting value. @@ -157,7 +208,7 @@ public: */ explicit Setting(const Type& default_val, const std::string& name) : BasicSetting<Type>(default_val, name) {} - ~Setting() = default; + virtual ~Setting() = default; /** * Tells this setting to represent either the global or custom setting when other member @@ -186,7 +237,13 @@ public: * * @returns The required value of the setting */ - [[nodiscard]] const Type& GetValue(bool need_global = false) const { + [[nodiscard]] virtual const Type& GetValue() const override { + if (use_global) { + return this->global; + } + return custom; + } + [[nodiscard]] virtual const Type& GetValue(bool need_global) const { if (use_global || need_global) { return this->global; } @@ -198,7 +255,7 @@ public: * * @param value The new value */ - void SetValue(const Type& value) { + void SetValue(const Type& value) override { Type temp{value}; if (use_global) { std::swap(this->global, temp); @@ -214,7 +271,7 @@ public: * * @returns A reference to the current setting value */ - const Type& operator=(const Type& value) { + const Type& operator=(const Type& value) override { Type temp{value}; if (use_global) { std::swap(this->global, temp); @@ -229,19 +286,88 @@ public: * * @returns A reference to the current setting value */ - explicit operator const Type&() const { + virtual explicit operator const Type&() const override { if (use_global) { return this->global; } return custom; } -private: +protected: bool use_global{true}; ///< The setting's global state Type custom{}; ///< The custom value of the setting }; /** + * RangedSetting is a Setting that implements a maximum and minimum value for its setting. Intended + * for use with quantifiable settings. + */ +template <typename Type> +class RangedSetting final : public BasicRangedSetting<Type>, public Setting<Type> { +public: + /** + * Sets a default value, minimum value, maximum value, and label. + * + * @param default_val Intial value of the setting, and default value of the setting + * @param min_val Sets the minimum allowed value of the setting + * @param max_val Sets the maximum allowed value of the setting + * @param name Label for the setting + */ + explicit RangedSetting(const Type& default_val, const Type& min_val, const Type& max_val, + const std::string& name) + : BasicSetting<Type>{default_val, name}, + BasicRangedSetting<Type>{default_val, min_val, max_val, name}, Setting<Type>{default_val, + name} {} + virtual ~RangedSetting() = default; + + // The following are needed to avoid a MSVC bug + // (source: https://stackoverflow.com/questions/469508) + [[nodiscard]] const Type& GetValue() const override { + return Setting<Type>::GetValue(); + } + [[nodiscard]] const Type& GetValue(bool need_global) const override { + return Setting<Type>::GetValue(need_global); + } + explicit operator const Type&() const override { + if (this->use_global) { + return this->global; + } + return this->custom; + } + + /** + * Like BasicSetting's SetValue, except value is clamped to the range of the setting. Sets the + * appropriate value depending on the global state. + * + * @param value The desired value + */ + void SetValue(const Type& value) override { + const Type temp = std::clamp(value, this->minimum, this->maximum); + if (this->use_global) { + this->global = temp; + } + this->custom = temp; + } + + /** + * Like BasicSetting's assignment overload, except value is clamped to the range of the setting. + * Uses the appropriate value depending on the global state. + * + * @param value The desired value + * @returns A reference to the setting's value + */ + const Type& operator=(const Type& value) override { + const Type temp = std::clamp(value, this->minimum, this->maximum); + if (this->use_global) { + this->global = temp; + return this->global; + } + this->custom = temp; + return this->custom; + } +}; + +/** * The InputSetting class allows for getting a reference to either the global or custom members. * This is required as we cannot easily modify the values of user-defined types within containers * using the SetValue() member function found in the Setting class. The primary purpose of this @@ -284,13 +410,14 @@ struct Values { BasicSetting<std::string> sink_id{"auto", "output_engine"}; BasicSetting<bool> audio_muted{false, "audio_muted"}; Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"}; - Setting<u8> volume{100, "volume"}; + RangedSetting<u8> volume{100, 0, 100, "volume"}; // Core Setting<bool> use_multi_core{true, "use_multi_core"}; // Cpu - Setting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, "cpu_accuracy"}; + RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, + CPUAccuracy::Unsafe, "cpu_accuracy"}; // TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021 BasicSetting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"}; BasicSetting<bool> cpu_debug_mode{false, "cpu_debug_mode"}; @@ -312,8 +439,10 @@ struct Values { Setting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"}; // Renderer - Setting<RendererBackend> renderer_backend{RendererBackend::OpenGL, "backend"}; + RangedSetting<RendererBackend> renderer_backend{ + RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; BasicSetting<bool> renderer_debug{false, "debug"}; + BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"}; BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"}; BasicSetting<bool> disable_shader_loop_safety_checks{false, "disable_shader_loop_safety_checks"}; @@ -322,26 +451,28 @@ struct Values { Setting<u16> resolution_factor{1, "resolution_factor"}; // *nix platforms may have issues with the borderless windowed fullscreen mode. // Default to exclusive fullscreen on these platforms for now. - Setting<int> fullscreen_mode{ + RangedSetting<FullscreenMode> fullscreen_mode{ #ifdef _WIN32 - 0, + FullscreenMode::Borderless, #else - 1, + FullscreenMode::Exclusive, #endif - "fullscreen_mode"}; - Setting<int> aspect_ratio{0, "aspect_ratio"}; - Setting<int> max_anisotropy{0, "max_anisotropy"}; + FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"}; + RangedSetting<int> aspect_ratio{0, 0, 3, "aspect_ratio"}; + RangedSetting<int> max_anisotropy{0, 0, 4, "max_anisotropy"}; Setting<bool> use_speed_limit{true, "use_speed_limit"}; - Setting<u16> speed_limit{100, "speed_limit"}; + RangedSetting<u16> speed_limit{100, 0, 9999, "speed_limit"}; Setting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"}; - Setting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, "gpu_accuracy"}; + RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, + GPUAccuracy::Extreme, "gpu_accuracy"}; Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"}; Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"}; Setting<bool> accelerate_astc{true, "accelerate_astc"}; Setting<bool> use_vsync{true, "use_vsync"}; - BasicSetting<u16> fps_cap{1000, "fps_cap"}; + BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"}; BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"}; - Setting<ShaderBackend> shader_backend{ShaderBackend::GLASM, "shader_backend"}; + RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL, + ShaderBackend::SPIRV, "shader_backend"}; Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; Setting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; Setting<bool> use_caches_gc{false, "use_caches_gc"}; @@ -358,10 +489,10 @@ struct Values { std::chrono::seconds custom_rtc_differential; BasicSetting<s32> current_user{0, "current_user"}; - Setting<s32> language_index{1, "language_index"}; - Setting<s32> region_index{1, "region_index"}; - Setting<s32> time_zone_index{0, "time_zone_index"}; - Setting<s32> sound_index{1, "sound_index"}; + RangedSetting<s32> language_index{1, 0, 17, "language_index"}; + RangedSetting<s32> region_index{1, 0, 6, "region_index"}; + RangedSetting<s32> time_zone_index{0, 0, 45, "time_zone_index"}; + RangedSetting<s32> sound_index{1, 0, 2, "sound_index"}; // Controls InputSetting<std::array<PlayerInput, 10>> players; @@ -378,7 +509,7 @@ struct Values { "udp_input_servers"}; BasicSetting<bool> mouse_panning{false, "mouse_panning"}; - BasicSetting<u8> mouse_panning_sensitivity{10, "mouse_panning_sensitivity"}; + BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; BasicSetting<bool> mouse_enabled{false, "mouse_enabled"}; std::string mouse_device; MouseButtonsRaw mouse_buttons; @@ -427,9 +558,10 @@ struct Values { BasicSetting<std::string> log_filter{"*:Info", "log_filter"}; BasicSetting<bool> use_dev_keys{false, "use_dev_keys"}; - // Services + // Network BasicSetting<std::string> bcat_backend{"none", "bcat_backend"}; BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"}; + BasicSetting<std::string> network_interface{std::string(), "network_interface"}; // WebService BasicSetting<bool> enable_telemetry{true, "enable_telemetry"}; diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index ad04df8ca..8430b9778 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -46,15 +46,13 @@ public: ElementPtr* new_ptr = new ElementPtr(); write_ptr->next.store(new_ptr, std::memory_order_release); write_ptr = new_ptr; + ++size; - const size_t previous_size{size++}; - - // Acquire the mutex and then immediately release it as a fence. + // cv_mutex must be held or else there will be a missed wakeup if the other thread is in the + // line before cv.wait // TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported. // See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details. - if (previous_size == 0) { - std::lock_guard lock{cv_mutex}; - } + std::lock_guard lock{cv_mutex}; cv.notify_one(); } diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp index 26db03fba..d7435a6e9 100644 --- a/src/common/uuid.cpp +++ b/src/common/uuid.cpp @@ -6,10 +6,64 @@ #include <fmt/format.h> +#include "common/assert.h" #include "common/uuid.h" namespace Common { +namespace { + +bool IsHexDigit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +u8 HexCharToByte(char c) { + if (c >= '0' && c <= '9') { + return static_cast<u8>(c - '0'); + } + if (c >= 'a' && c <= 'f') { + return static_cast<u8>(c - 'a' + 10); + } + if (c >= 'A' && c <= 'F') { + return static_cast<u8>(c - 'A' + 10); + } + ASSERT_MSG(false, "{} is not a hexadecimal digit!", c); + return u8{0}; +} + +} // Anonymous namespace + +u128 HexStringToU128(std::string_view hex_string) { + const size_t length = hex_string.length(); + + // Detect "0x" prefix. + const bool has_0x_prefix = length > 2 && hex_string[0] == '0' && hex_string[1] == 'x'; + const size_t offset = has_0x_prefix ? 2 : 0; + + // Check length. + if (length > 32 + offset) { + ASSERT_MSG(false, "hex_string has more than 32 hexadecimal characters!"); + return INVALID_UUID; + } + + u64 lo = 0; + u64 hi = 0; + for (size_t i = 0; i < length - offset; ++i) { + const char c = hex_string[length - 1 - i]; + if (!IsHexDigit(c)) { + ASSERT_MSG(false, "{} is not a hexadecimal digit!", c); + return INVALID_UUID; + } + if (i < 16) { + lo |= u64{HexCharToByte(c)} << (i * 4); + } + if (i >= 16) { + hi |= u64{HexCharToByte(c)} << ((i - 16) * 4); + } + } + return u128{lo, hi}; +} + UUID UUID::Generate() { std::random_device device; std::mt19937 gen(device()); @@ -18,7 +72,7 @@ UUID UUID::Generate() { } std::string UUID::Format() const { - return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); + return fmt::format("{:016x}{:016x}", uuid[1], uuid[0]); } std::string UUID::FormatSwitch() const { diff --git a/src/common/uuid.h b/src/common/uuid.h index 0ffa37e7c..2353179d8 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -5,6 +5,7 @@ #pragma once #include <string> +#include <string_view> #include "common/common_types.h" @@ -12,12 +13,30 @@ namespace Common { constexpr u128 INVALID_UUID{{0, 0}}; +/** + * Converts a hex string to a 128-bit unsigned integer. + * + * The hex string can be formatted in lowercase or uppercase, with or without the "0x" prefix. + * + * This function will assert and return INVALID_UUID under the following conditions: + * - If the hex string is more than 32 characters long + * - If the hex string contains non-hexadecimal characters + * + * @param hex_string Hexadecimal string + * + * @returns A 128-bit unsigned integer if successfully converted, INVALID_UUID otherwise. + */ +[[nodiscard]] u128 HexStringToU128(std::string_view hex_string); + struct UUID { // UUIDs which are 0 are considered invalid! u128 uuid; UUID() = default; constexpr explicit UUID(const u128& id) : uuid{id} {} constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} + explicit UUID(std::string_view hex_string) { + uuid = HexStringToU128(hex_string); + } [[nodiscard]] constexpr explicit operator bool() const { return uuid != INVALID_UUID; @@ -50,3 +69,14 @@ struct UUID { static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); } // namespace Common + +namespace std { + +template <> +struct hash<Common::UUID> { + size_t operator()(const Common::UUID& uuid) const noexcept { + return uuid.uuid[1] ^ uuid.uuid[0]; + } +}; + +} // namespace std diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index c2c9b6134..0ddf9b83e 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -6,7 +6,7 @@ #include <bitset> #include <initializer_list> -#include <xbyak.h> +#include <xbyak/xbyak.h> #include "common/assert.h" namespace Common::X64 { diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h index df17f8cbe..44d2558f1 100644 --- a/src/common/x64/xbyak_util.h +++ b/src/common/x64/xbyak_util.h @@ -5,7 +5,7 @@ #pragma once #include <type_traits> -#include <xbyak.h> +#include <xbyak/xbyak.h> #include "common/x64/xbyak_abi.h" namespace Common::X64 { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5c99c00f5..f5cf5c16a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -636,6 +636,8 @@ add_library(core STATIC memory.h network/network.cpp network/network.h + network/network_interface.cpp + network/network_interface.h network/sockets.h perf_stats.cpp perf_stats.h diff --git a/src/core/core.cpp b/src/core/core.cpp index d3e84c4ef..b0dc594d4 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -84,8 +84,6 @@ FileSys::StorageId GetStorageIdForFrontendSlot( } // Anonymous namespace -/*static*/ System System::s_instance; - FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, const std::string& path) { // To account for split 00+01+etc files. @@ -425,6 +423,13 @@ struct System::Impl { System::System() : impl{std::make_unique<Impl>(*this)} {} System::~System() = default; +void System::InitializeGlobalInstance() { + if (s_instance) { + abort(); + } + s_instance = std::unique_ptr<System>(new System); +} + CpuManager& System::GetCpuManager() { return impl->cpu_manager; } @@ -494,12 +499,6 @@ const ARM_Interface& System::CurrentArmInterface() const { return impl->kernel.CurrentPhysicalCore().ArmInterface(); } -std::size_t System::CurrentCoreIndex() const { - std::size_t core = impl->kernel.GetCurrentHostThreadID(); - ASSERT(core < Core::Hardware::NUM_CPU_CORES); - return core; -} - Kernel::PhysicalCore& System::CurrentPhysicalCore() { return impl->kernel.CurrentPhysicalCore(); } diff --git a/src/core/core.h b/src/core/core.h index ea143043c..65b447a1c 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -121,9 +121,14 @@ public: * @returns Reference to the instance of the System singleton class. */ [[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() { - return s_instance; + if (!s_instance) { + abort(); + } + return *s_instance; } + static void InitializeGlobalInstance(); + /// Enumeration representing the return values of the System Initialize and Load process. enum class ResultStatus : u32 { Success, ///< Succeeded @@ -205,9 +210,6 @@ public: /// Gets an ARM interface to the CPU core that is currently running [[nodiscard]] const ARM_Interface& CurrentArmInterface() const; - /// Gets the index of the currently running CPU core - [[nodiscard]] std::size_t CurrentCoreIndex() const; - /// Gets the physical core for the CPU core that is currently running [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore(); @@ -396,7 +398,7 @@ private: struct Impl; std::unique_ptr<Impl> impl; - static System s_instance; + inline static std::unique_ptr<System> s_instance{}; }; } // namespace Core diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 7e195346b..de2e5563e 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -21,34 +21,25 @@ namespace Core { CpuManager::CpuManager(System& system_) : system{system_} {} CpuManager::~CpuManager() = default; -void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) { - cpu_manager.RunThread(core); +void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, + std::size_t core) { + cpu_manager.RunThread(stop_token, core); } void CpuManager::Initialize() { running_mode = true; if (is_multicore) { for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - core_data[core].host_thread = - std::make_unique<std::thread>(ThreadStart, std::ref(*this), core); + core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core); } } else { - core_data[0].host_thread = std::make_unique<std::thread>(ThreadStart, std::ref(*this), 0); + core_data[0].host_thread = std::jthread(ThreadStart, std::ref(*this), 0); } } void CpuManager::Shutdown() { running_mode = false; Pause(false); - if (is_multicore) { - for (auto& data : core_data) { - data.host_thread->join(); - data.host_thread.reset(); - } - } else { - core_data[0].host_thread->join(); - core_data[0].host_thread.reset(); - } } std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() { @@ -127,17 +118,18 @@ void CpuManager::MultiCoreRunGuestLoop() { physical_core = &kernel.CurrentPhysicalCore(); } system.ExitDynarmicProfile(); - physical_core->ArmInterface().ClearExclusiveState(); - kernel.CurrentScheduler()->RescheduleCurrentCore(); + { + Kernel::KScopedDisableDispatch dd(kernel); + physical_core->ArmInterface().ClearExclusiveState(); + } } } void CpuManager::MultiCoreRunIdleThread() { auto& kernel = system.Kernel(); while (true) { - auto& physical_core = kernel.CurrentPhysicalCore(); - physical_core.Idle(); - kernel.CurrentScheduler()->RescheduleCurrentCore(); + Kernel::KScopedDisableDispatch dd(kernel); + kernel.CurrentPhysicalCore().Idle(); } } @@ -145,12 +137,12 @@ void CpuManager::MultiCoreRunSuspendThread() { auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); while (true) { - auto core = kernel.GetCurrentHostThreadID(); + auto core = kernel.CurrentPhysicalCoreIndex(); auto& scheduler = *kernel.CurrentScheduler(); Kernel::KThread* current_thread = scheduler.GetCurrentThread(); Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); ASSERT(scheduler.ContextSwitchPending()); - ASSERT(core == kernel.GetCurrentHostThreadID()); + ASSERT(core == kernel.CurrentPhysicalCoreIndex()); scheduler.RescheduleCurrentCore(); } } @@ -317,7 +309,7 @@ void CpuManager::Pause(bool paused) { } } -void CpuManager::RunThread(std::size_t core) { +void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) { /// Initialization system.RegisterCoreThread(core); std::string name; @@ -356,8 +348,8 @@ void CpuManager::RunThread(std::size_t core) { sc_sync_first_use = false; } - // Abort if emulation was killed before the session really starts - if (!system.IsPoweredOn()) { + // Emulation was stopped + if (stop_token.stop_requested()) { return; } diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index 140263b09..9d92d4af0 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -78,9 +78,9 @@ private: void SingleCoreRunSuspendThread(); void SingleCorePause(bool paused); - static void ThreadStart(CpuManager& cpu_manager, std::size_t core); + static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); - void RunThread(std::size_t core); + void RunThread(std::stop_token stop_token, std::size_t core); struct CoreData { std::shared_ptr<Common::Fiber> host_context; @@ -89,7 +89,7 @@ private: std::atomic<bool> is_running; std::atomic<bool> is_paused; std::atomic<bool> initialized; - std::unique_ptr<std::thread> host_thread; + std::jthread host_thread; }; std::atomic<bool> running_mode{}; diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index 5e10a7ad9..43d5670a9 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -12,9 +12,9 @@ namespace HLE::ApiVersion { // Horizon OS version constants. -constexpr u8 HOS_VERSION_MAJOR = 11; -constexpr u8 HOS_VERSION_MINOR = 0; -constexpr u8 HOS_VERSION_MICRO = 1; +constexpr u8 HOS_VERSION_MAJOR = 12; +constexpr u8 HOS_VERSION_MINOR = 1; +constexpr u8 HOS_VERSION_MICRO = 0; // NintendoSDK version constants. @@ -22,15 +22,15 @@ constexpr u8 SDK_REVISION_MAJOR = 1; constexpr u8 SDK_REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf"; -constexpr char DISPLAY_VERSION[] = "11.0.1"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0"; +constexpr char VERSION_HASH[] = "76b10c2dab7d3aa73fc162f8dff1655e6a21caf4"; +constexpr char DISPLAY_VERSION[] = "12.1.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 12.1.0-1.0"; // Atmosphere version constants. constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0; constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 4; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 5; constexpr u32 GetTargetFirmware() { return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 | diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp index 1b429bc1e..6771ef621 100644 --- a/src/core/hle/kernel/k_address_arbiter.cpp +++ b/src/core/hle/kernel/k_address_arbiter.cpp @@ -28,7 +28,7 @@ bool ReadFromUser(Core::System& system, s32* out, VAddr address) { bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) { auto& monitor = system.Monitor(); - const auto current_core = system.CurrentCoreIndex(); + const auto current_core = system.Kernel().CurrentPhysicalCoreIndex(); // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. // TODO(bunnei): We should call CanAccessAtomic(..) here. @@ -58,7 +58,7 @@ bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 valu bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) { auto& monitor = system.Monitor(); - const auto current_core = system.CurrentCoreIndex(); + const auto current_core = system.Kernel().CurrentPhysicalCoreIndex(); // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. // TODO(bunnei): We should call CanAccessAtomic(..) here. diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index e4fcdbc67..165b76747 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -170,6 +170,10 @@ public: } } + const std::string& GetName() const { + return name; + } + private: void RegisterWithKernel(); void UnregisterWithKernel(); diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index ef14ad1d2..4174f35fd 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -35,7 +35,7 @@ bool WriteToUser(Core::System& system, VAddr address, const u32* p) { bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero, u32 new_orr_mask) { auto& monitor = system.Monitor(); - const auto current_core = system.CurrentCoreIndex(); + const auto current_core = system.Kernel().CurrentPhysicalCoreIndex(); // Load the value from the address. const auto expected = monitor.ExclusiveRead32(current_core, address); diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp index 6a420d5b0..d720c2dda 100644 --- a/src/core/hle/kernel/k_handle_table.cpp +++ b/src/core/hle/kernel/k_handle_table.cpp @@ -13,6 +13,7 @@ ResultCode KHandleTable::Finalize() { // Get the table and clear our record of it. u16 saved_table_size = 0; { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); std::swap(m_table_size, saved_table_size); @@ -43,6 +44,7 @@ bool KHandleTable::Remove(Handle handle) { // Find the object and free the entry. KAutoObject* obj = nullptr; { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); if (this->IsValidHandle(handle)) { @@ -61,6 +63,7 @@ bool KHandleTable::Remove(Handle handle) { } ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); // Never exceed our capacity. @@ -83,6 +86,7 @@ ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) { } ResultCode KHandleTable::Reserve(Handle* out_handle) { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); // Never exceed our capacity. @@ -93,6 +97,7 @@ ResultCode KHandleTable::Reserve(Handle* out_handle) { } void KHandleTable::Unreserve(Handle handle) { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); // Unpack the handle. @@ -111,6 +116,7 @@ void KHandleTable::Unreserve(Handle handle) { } void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) { + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); // Unpack the handle. diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h index 2ff6aa160..75dcec7df 100644 --- a/src/core/hle/kernel/k_handle_table.h +++ b/src/core/hle/kernel/k_handle_table.h @@ -69,6 +69,7 @@ public: template <typename T = KAutoObject> KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const { // Lock and look up in table. + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); if constexpr (std::is_same_v<T, KAutoObject>) { @@ -123,6 +124,7 @@ public: size_t num_opened; { // Lock the table. + KScopedDisableDispatch dd(kernel); KScopedSpinLock lk(m_lock); for (num_opened = 0; num_opened < num_handles; num_opened++) { // Get the current handle. diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 8ead1a769..3d7e6707e 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -59,6 +59,7 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority thread->GetContext64().cpu_registers[0] = 0; thread->GetContext32().cpu_registers[1] = thread_handle; thread->GetContext64().cpu_registers[1] = thread_handle; + thread->DisableDispatch(); auto& kernel = system.Kernel(); // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index 6a7d80d03..6ddbae52c 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -376,20 +376,18 @@ void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) { } void KScheduler::DisableScheduling(KernelCore& kernel) { - if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { - ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0); - scheduler->GetCurrentThread()->DisableDispatch(); - } + ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0); + GetCurrentThreadPointer(kernel)->DisableDispatch(); } void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { - if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { - ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1); - if (scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1) { - scheduler->GetCurrentThread()->EnableDispatch(); - } + ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 1); + + if (GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() > 1) { + GetCurrentThreadPointer(kernel)->EnableDispatch(); + } else { + RescheduleCores(kernel, cores_needing_scheduling); } - RescheduleCores(kernel, cores_needing_scheduling); } u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { @@ -617,13 +615,17 @@ KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, c state.highest_priority_thread = nullptr; } -KScheduler::~KScheduler() { +void KScheduler::Finalize() { if (idle_thread) { idle_thread->Close(); idle_thread = nullptr; } } +KScheduler::~KScheduler() { + ASSERT(!idle_thread); +} + KThread* KScheduler::GetCurrentThread() const { if (auto result = current_thread.load(); result) { return result; @@ -642,10 +644,12 @@ void KScheduler::RescheduleCurrentCore() { if (phys_core.IsInterrupted()) { phys_core.ClearInterrupt(); } + guard.Lock(); if (state.needs_scheduling.load()) { Schedule(); } else { + GetCurrentThread()->EnableDispatch(); guard.Unlock(); } } @@ -655,26 +659,33 @@ void KScheduler::OnThreadStart() { } void KScheduler::Unload(KThread* thread) { + ASSERT(thread); + LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr"); - if (thread) { - if (thread->IsCallingSvc()) { - thread->ClearIsCallingSvc(); - } - if (!thread->IsTerminationRequested()) { - prev_thread = thread; - - Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); - cpu_core.SaveContext(thread->GetContext32()); - cpu_core.SaveContext(thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - } else { - prev_thread = nullptr; - } - thread->context_guard.Unlock(); + if (thread->IsCallingSvc()) { + thread->ClearIsCallingSvc(); } + + auto& physical_core = system.Kernel().PhysicalCore(core_id); + if (!physical_core.IsInitialized()) { + return; + } + + Core::ARM_Interface& cpu_core = physical_core.ArmInterface(); + cpu_core.SaveContext(thread->GetContext32()); + cpu_core.SaveContext(thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + + if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) { + prev_thread = thread; + } else { + prev_thread = nullptr; + } + + thread->context_guard.Unlock(); } void KScheduler::Reload(KThread* thread) { @@ -683,11 +694,6 @@ void KScheduler::Reload(KThread* thread) { if (thread) { ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable."); - auto* const thread_owner_process = thread->GetOwnerProcess(); - if (thread_owner_process != nullptr) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); cpu_core.LoadContext(thread->GetContext32()); cpu_core.LoadContext(thread->GetContext64()); @@ -705,7 +711,7 @@ void KScheduler::SwitchContextStep2() { } void KScheduler::ScheduleImpl() { - KThread* previous_thread = current_thread.load(); + KThread* previous_thread = GetCurrentThread(); KThread* next_thread = state.highest_priority_thread; state.needs_scheduling = false; @@ -717,10 +723,15 @@ void KScheduler::ScheduleImpl() { // If we're not actually switching thread, there's nothing to do. if (next_thread == current_thread.load()) { + previous_thread->EnableDispatch(); guard.Unlock(); return; } + if (next_thread->GetCurrentCore() != core_id) { + next_thread->SetCurrentCore(core_id); + } + current_thread.store(next_thread); KProcess* const previous_process = system.Kernel().CurrentProcess(); @@ -731,11 +742,7 @@ void KScheduler::ScheduleImpl() { Unload(previous_thread); std::shared_ptr<Common::Fiber>* old_context; - if (previous_thread != nullptr) { - old_context = &previous_thread->GetHostContext(); - } else { - old_context = &idle_thread->GetHostContext(); - } + old_context = &previous_thread->GetHostContext(); guard.Unlock(); Common::Fiber::YieldTo(*old_context, *switch_fiber); diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h index 12cfae919..516e0cdba 100644 --- a/src/core/hle/kernel/k_scheduler.h +++ b/src/core/hle/kernel/k_scheduler.h @@ -33,6 +33,8 @@ public: explicit KScheduler(Core::System& system_, s32 core_id_); ~KScheduler(); + void Finalize(); + /// Reschedules to the next available thread (call after current thread is suspended) void RescheduleCurrentCore(); diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 9f1d3156b..0f6808ade 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -14,6 +14,7 @@ #include "common/fiber.h" #include "common/logging/log.h" #include "common/scope_exit.h" +#include "common/settings.h" #include "common/thread_queue_list.h" #include "core/core.h" #include "core/cpu_manager.h" @@ -188,7 +189,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s // Setup the stack parameters. StackParameters& sp = GetStackParameters(); sp.cur_thread = this; - sp.disable_count = 1; + sp.disable_count = 0; SetInExceptionHandler(); // Set thread ID. @@ -215,9 +216,10 @@ ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uint // Initialize the thread. R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type)); - // Initialize host context. + // Initialize emulation parameters. thread->host_context = std::make_shared<Common::Fiber>(std::move(init_func), init_func_parameter); + thread->is_single_core = !Settings::values.use_multi_core.GetValue(); return ResultSuccess; } @@ -970,6 +972,9 @@ ResultCode KThread::Run() { // Set our state and finish. SetState(ThreadState::Runnable); + + DisableDispatch(); + return ResultSuccess; } } @@ -1054,4 +1059,16 @@ s32 GetCurrentCoreId(KernelCore& kernel) { return GetCurrentThread(kernel).GetCurrentCore(); } +KScopedDisableDispatch::~KScopedDisableDispatch() { + if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) { + auto scheduler = kernel.CurrentScheduler(); + + if (scheduler) { + scheduler->RescheduleCurrentCore(); + } + } else { + GetCurrentThread(kernel).EnableDispatch(); + } +} + } // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index c77f44ad4..e4c4c877d 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -450,16 +450,39 @@ public: sleeping_queue = q; } + [[nodiscard]] bool IsKernelThread() const { + return GetActiveCore() == 3; + } + + [[nodiscard]] bool IsDispatchTrackingDisabled() const { + return is_single_core || IsKernelThread(); + } + [[nodiscard]] s32 GetDisableDispatchCount() const { + if (IsDispatchTrackingDisabled()) { + // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. + return 1; + } + return this->GetStackParameters().disable_count; } void DisableDispatch() { + if (IsDispatchTrackingDisabled()) { + // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. + return; + } + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); this->GetStackParameters().disable_count++; } void EnableDispatch() { + if (IsDispatchTrackingDisabled()) { + // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. + return; + } + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); this->GetStackParameters().disable_count--; } @@ -708,6 +731,7 @@ private: // For emulation std::shared_ptr<Common::Fiber> host_context{}; + bool is_single_core{}; // For debugging std::vector<KSynchronizationObject*> wait_objects_for_debugging; @@ -752,4 +776,16 @@ public: } }; +class KScopedDisableDispatch { +public: + [[nodiscard]] explicit KScopedDisableDispatch(KernelCore& kernel_) : kernel{kernel_} { + GetCurrentThread(kernel).DisableDispatch(); + } + + ~KScopedDisableDispatch(); + +private: + KernelCore& kernel; +}; + } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 92fbc5532..8673384ee 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -85,8 +85,9 @@ struct KernelCore::Impl { } void InitializeCores() { - for (auto& core : cores) { - core.Initialize(current_process->Is64BitProcess()); + for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + cores[core_id].Initialize(current_process->Is64BitProcess()); + system.Memory().SetCurrentPageTable(*current_process, core_id); } } @@ -131,15 +132,6 @@ struct KernelCore::Impl { next_user_process_id = KProcess::ProcessIDMin; next_thread_id = 1; - for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { - if (suspend_threads[core_id]) { - suspend_threads[core_id]->Close(); - suspend_threads[core_id] = nullptr; - } - - schedulers[core_id].reset(); - } - cores.clear(); global_handle_table->Finalize(); @@ -167,6 +159,16 @@ struct KernelCore::Impl { CleanupObject(time_shared_mem); CleanupObject(system_resource_limit); + for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + if (suspend_threads[core_id]) { + suspend_threads[core_id]->Close(); + suspend_threads[core_id] = nullptr; + } + + schedulers[core_id]->Finalize(); + schedulers[core_id].reset(); + } + // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others next_host_thread_id = Core::Hardware::NUM_CPU_CORES; @@ -257,14 +259,6 @@ struct KernelCore::Impl { void MakeCurrentProcess(KProcess* process) { current_process = process; - if (process == nullptr) { - return; - } - - const u32 core_id = GetCurrentHostThreadID(); - if (core_id < Core::Hardware::NUM_CPU_CORES) { - system.Memory().SetCurrentPageTable(*process, core_id); - } } /// Creates a new host thread ID, should only be called by GetHostThreadId @@ -824,16 +818,20 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const { return impl->cores[id]; } +size_t KernelCore::CurrentPhysicalCoreIndex() const { + const u32 core_id = impl->GetCurrentHostThreadID(); + if (core_id >= Core::Hardware::NUM_CPU_CORES) { + return Core::Hardware::NUM_CPU_CORES - 1; + } + return core_id; +} + Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() { - u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return impl->cores[core_id]; + return impl->cores[CurrentPhysicalCoreIndex()]; } const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { - u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return impl->cores[core_id]; + return impl->cores[CurrentPhysicalCoreIndex()]; } Kernel::KScheduler* KernelCore::CurrentScheduler() { @@ -1026,6 +1024,9 @@ void KernelCore::Suspend(bool in_suspention) { impl->suspend_threads[core_id]->SetState(state); impl->suspend_threads[core_id]->SetWaitReasonForDebugging( ThreadWaitReasonForDebugging::Suspended); + if (!should_suspend) { + impl->suspend_threads[core_id]->DisableDispatch(); + } } } } @@ -1040,13 +1041,11 @@ void KernelCore::ExceptionalExit() { } void KernelCore::EnterSVCProfile() { - std::size_t core = impl->GetCurrentHostThreadID(); - impl->svc_ticks[core] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC)); + impl->svc_ticks[CurrentPhysicalCoreIndex()] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC)); } void KernelCore::ExitSVCProfile() { - std::size_t core = impl->GetCurrentHostThreadID(); - MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]); + MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[CurrentPhysicalCoreIndex()]); } std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 3a6db0b1c..57535433b 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -146,6 +146,9 @@ public: /// Gets the an instance of the respective physical CPU core. const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const; + /// Gets the current physical core index for the running host thread. + std::size_t CurrentPhysicalCoreIndex() const; + /// Gets the sole instance of the Scheduler at the current running core. Kernel::KScheduler* CurrentScheduler(); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 2eb532472..890c52198 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -877,7 +877,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle const u64 thread_ticks = current_thread->GetCpuTime(); out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); - } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) { + } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; } @@ -1078,8 +1078,8 @@ static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Hand for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetCurrentThread()) { current = true; + break; } - break; } // If the thread is current, retry until it isn't. diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 2e969f2a8..882fc1492 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -292,7 +292,7 @@ public: protected: void Get(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.Format()); ProfileBase profile_base{}; ProfileData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { @@ -301,7 +301,7 @@ protected: rb.Push(ResultSuccess); rb.PushRaw(profile_base); } else { - LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}", + LOG_ERROR(Service_ACC, "Failed to get profile base and data for user=0x{}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultUnknown); // TODO(ogniK): Get actual error code @@ -309,14 +309,14 @@ protected: } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.Format()); ProfileBase profile_base{}; if (profile_manager.GetProfileBase(user_id, profile_base)) { IPC::ResponseBuilder rb{ctx, 16}; rb.Push(ResultSuccess); rb.PushRaw(profile_base); } else { - LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format()); + LOG_ERROR(Service_ACC, "Failed to get profile base for user=0x{}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultUnknown); // TODO(ogniK): Get actual error code } @@ -372,7 +372,7 @@ protected: const auto user_data = ctx.ReadBuffer(); - LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}", Common::StringFromFixedZeroTerminatedBuffer( reinterpret_cast<const char*>(base.username.data()), base.username.size()), base.timestamp, base.user_uuid.Format()); @@ -405,7 +405,7 @@ protected: const auto user_data = ctx.ReadBuffer(); const auto image_data = ctx.ReadBuffer(1); - LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}", Common::StringFromFixedZeroTerminatedBuffer( reinterpret_cast<const char*>(base.username.data()), base.username.size()), base.timestamp, base.user_uuid.Format()); @@ -662,7 +662,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; Common::UUID user_id = rp.PopRaw<Common::UUID>(); - LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -693,7 +693,7 @@ void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; Common::UUID user_id = rp.PopRaw<Common::UUID>(); - LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -802,7 +802,7 @@ void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; Common::UUID user_id = rp.PopRaw<Common::UUID>(); - LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called, user_id=0x{}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -844,7 +844,7 @@ void Module::Interface::StoreSaveDataThumbnailApplication(Kernel::HLERequestCont IPC::RequestParser rp{ctx}; const auto uuid = rp.PopRaw<Common::UUID>(); - LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}", uuid.Format()); + LOG_WARNING(Service_ACC, "(STUBBED) called, uuid=0x{}", uuid.Format()); // TODO(ogniK): Check if application ID is zero on acc initialize. As we don't have a reliable // way of confirming things like the TID, we're going to assume a non zero value for the time @@ -858,7 +858,7 @@ void Module::Interface::StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& const auto uuid = rp.PopRaw<Common::UUID>(); const auto tid = rp.Pop<u64_le>(); - LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}, tid={:016X}", uuid.Format(), tid); + LOG_WARNING(Service_ACC, "(STUBBED) called, uuid=0x{}, tid={:016X}", uuid.Format(), tid); StoreSaveDataThumbnail(ctx, uuid, tid); } diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp index ef6854d62..36a4aa9cd 100644 --- a/src/core/hle/service/am/applets/applet_error.cpp +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -16,6 +16,30 @@ namespace Service::AM::Applets { +struct ErrorCode { + u32 error_category{}; + u32 error_number{}; + + static constexpr ErrorCode FromU64(u64 error_code) { + return { + .error_category{static_cast<u32>(error_code >> 32)}, + .error_number{static_cast<u32>(error_code & 0xFFFFFFFF)}, + }; + } + + static constexpr ErrorCode FromResultCode(ResultCode result) { + return { + .error_category{2000 + static_cast<u32>(result.module.Value())}, + .error_number{result.description.Value()}, + }; + } + + constexpr ResultCode ToResultCode() const { + return ResultCode{static_cast<ErrorModule>(error_category - 2000), error_number}; + } +}; +static_assert(sizeof(ErrorCode) == 0x8, "ErrorCode has incorrect size."); + #pragma pack(push, 4) struct ShowError { u8 mode; @@ -76,12 +100,7 @@ void CopyArgumentData(const std::vector<u8>& data, T& variable) { } ResultCode Decode64BitError(u64 error) { - const auto description = (error >> 32) & 0x1FFF; - auto module = error & 0x3FF; - if (module >= 2000) - module -= 2000; - module &= 0x1FF; - return {static_cast<ErrorModule>(module), static_cast<u32>(description)}; + return ErrorCode::FromU64(error).ToResultCode(); } } // Anonymous namespace diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp index 7cae90609..c89aa1bbf 100644 --- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp +++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp @@ -377,7 +377,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { if (swkbd_config_common.use_utf8) { std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); - const u64 buffer_size = sizeof(u64) + utf8_submitted_text.size(); + // Include the null terminator in the buffer size. + const u64 buffer_size = utf8_submitted_text.size() + 1; LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, utf8_submitted_text); @@ -386,7 +387,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), utf8_submitted_text.size()); } else { - const u64 buffer_size = sizeof(u64) + current_text.size() * sizeof(char16_t); + // Include the null terminator in the buffer size. + const u64 buffer_size = (current_text.size() + 1) * sizeof(char16_t); LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, Common::UTF16ToUTF8(current_text)); diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index a3c939c0c..b58c152ce 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -158,7 +158,7 @@ private: const auto local_play = rp.Pop<bool>(); const auto uuid = rp.PopRaw<Common::UUID>(); - LOG_WARNING(Service_Friend, "(STUBBED) called local_play={} uuid={}", local_play, + LOG_WARNING(Service_Friend, "(STUBBED) called, local_play={}, uuid=0x{}", local_play, uuid.Format()); IPC::ResponseBuilder rb{ctx, 2}; @@ -171,7 +171,7 @@ private: const auto uuid = rp.PopRaw<Common::UUID>(); [[maybe_unused]] const auto filter = rp.PopRaw<SizedFriendFilter>(); const auto pid = rp.Pop<u64>(); - LOG_WARNING(Service_Friend, "(STUBBED) called, offset={}, uuid={}, pid={}", friend_offset, + LOG_WARNING(Service_Friend, "(STUBBED) called, offset={}, uuid=0x{}, pid={}", friend_offset, uuid.Format(), pid); IPC::ResponseBuilder rb{ctx, 3}; @@ -289,7 +289,7 @@ void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx IPC::RequestParser rp{ctx}; auto uuid = rp.PopRaw<Common::UUID>(); - LOG_DEBUG(Service_Friend, "called, uuid={}", uuid.Format()); + LOG_DEBUG(Service_Friend, "called, uuid=0x{}", uuid.Format()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index e742db48f..0a53c0c81 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -11,6 +11,7 @@ #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/service.h" #include "core/network/network.h" +#include "core/network/network_interface.h" namespace Service::NIFM { @@ -179,10 +180,10 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - if (Settings::values.bcat_backend.GetValue() == "none") { - rb.PushEnum(RequestState::NotSubmitted); - } else { + if (Network::GetHostIPv4Address().has_value()) { rb.PushEnum(RequestState::Connected); + } else { + rb.PushEnum(RequestState::NotSubmitted); } } @@ -322,12 +323,15 @@ private: void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); - const auto [ipv4, error] = Network::GetHostIPv4Address(); - UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS); + auto ipv4 = Network::GetHostIPv4Address(); + if (!ipv4) { + LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0"); + ipv4.emplace(Network::IPv4Address{0, 0, 0, 0}); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushRaw(ipv4); + rb.PushRaw(*ipv4); } void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -354,10 +358,10 @@ private: static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), "IpConfigInfo has incorrect size."); - const IpConfigInfo ip_config_info{ + IpConfigInfo ip_config_info{ .ip_address_setting{ .is_automatic{true}, - .current_address{192, 168, 1, 100}, + .current_address{0, 0, 0, 0}, .subnet_mask{255, 255, 255, 0}, .gateway{192, 168, 1, 1}, }, @@ -368,6 +372,19 @@ private: }, }; + const auto iface = Network::GetSelectedNetworkInterface(); + if (iface) { + ip_config_info.ip_address_setting = + IpAddressSetting{.is_automatic{true}, + .current_address{Network::TranslateIPv4(iface->ip_address)}, + .subnet_mask{Network::TranslateIPv4(iface->subnet_mask)}, + .gateway{Network::TranslateIPv4(iface->gateway)}}; + + } else { + LOG_ERROR(Service_NIFM, + "Couldn't get host network configuration info, using default values"); + } + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; rb.Push(ResultSuccess); rb.PushRaw<IpConfigInfo>(ip_config_info); @@ -384,10 +401,10 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - if (Settings::values.bcat_backend.GetValue() == "none") { - rb.Push<u8>(0); - } else { + if (Network::GetHostIPv4Address().has_value()) { rb.Push<u8>(1); + } else { + rb.Push<u8>(0); } } void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { @@ -395,10 +412,10 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - if (Settings::values.bcat_backend.GetValue() == "none") { - rb.Push<u8>(0); - } else { + if (Network::GetHostIPv4Address().has_value()) { rb.Push<u8>(1); + } else { + rb.Push<u8>(0); } } }; diff --git a/src/core/hle/service/ns/language.cpp b/src/core/hle/service/ns/language.cpp index 29c4a820c..7d9e4a20b 100644 --- a/src/core/hle/service/ns/language.cpp +++ b/src/core/hle/service/ns/language.cpp @@ -339,13 +339,16 @@ std::optional<ApplicationLanguage> ConvertToApplicationLanguage( case Set::LanguageCode::FR_CA: return ApplicationLanguage::CanadianFrench; case Set::LanguageCode::PT: + case Set::LanguageCode::PT_BR: return ApplicationLanguage::Portuguese; case Set::LanguageCode::RU: return ApplicationLanguage::Russian; case Set::LanguageCode::KO: return ApplicationLanguage::Korean; + case Set::LanguageCode::ZH_TW: case Set::LanguageCode::ZH_HANT: return ApplicationLanguage::TraditionalChinese; + case Set::LanguageCode::ZH_CN: case Set::LanguageCode::ZH_HANS: return ApplicationLanguage::SimplifiedChinese; default: diff --git a/src/core/hle/service/ns/ns_language.h b/src/core/hle/service/ns/ns_language.h deleted file mode 100644 index 59ac85a19..000000000 --- a/src/core/hle/service/ns/ns_language.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once -#include <optional> -#include <string> -#include "common/common_types.h" -#include "core/hle/service/set/set.h" - -namespace Service::NS { -/// This is nn::ns::detail::ApplicationLanguage -enum class ApplicationLanguage : u8 { - AmericanEnglish = 0, - BritishEnglish, - Japanese, - French, - German, - LatinAmericanSpanish, - Spanish, - Italian, - Dutch, - CanadianFrench, - Portuguese, - Russian, - Korean, - TraditionalChinese, - SimplifiedChinese, - Count -}; -using ApplicationLanguagePriorityList = - const std::array<ApplicationLanguage, static_cast<std::size_t>(ApplicationLanguage::Count)>; - -constexpr u32 GetSupportedLanguageFlag(const ApplicationLanguage lang) { - return 1U << static_cast<u32>(lang); -} - -const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(ApplicationLanguage lang); -std::optional<ApplicationLanguage> ConvertToApplicationLanguage( - Service::Set::LanguageCode language_code); -std::optional<Service::Set::LanguageCode> ConvertToLanguageCode(ApplicationLanguage lang); -} // namespace Service::NS
\ No newline at end of file diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp index 1403a39d0..845de724d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp @@ -166,8 +166,6 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); } else { cmd_buffer.map_address = object->dma_map_addr; - AddBufferMap(object->dma_map_addr, object->size, object->addr, - object->status == nvmap::Object::Status::Allocated); } } std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); @@ -178,30 +176,11 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto } NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { - IoctlMapBuffer params{}; - std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); - std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); - SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); - - auto& gpu = system.GPU(); - - for (auto& cmd_buffer : cmd_buffer_handles) { - const auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)}; - if (!object) { - LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle); - std::memcpy(output.data(), ¶ms, output.size()); - return NvResult::InvalidState; - } - if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) { - gpu.MemoryManager().Unmap(object->dma_map_addr, *size); - } else { - // This occurs quite frequently, however does not seem to impact functionality - LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr, - object->dma_map_addr); - } - object->dma_map_addr = 0; - } + // This is intntionally stubbed. + // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame + // addresses, and risk invalidating data before the async GPU thread is done with it std::memset(output.data(), 0, output.size()); + LOG_DEBUG(Service_NVDRV, "(STUBBED) called"); return NvResult::Success; } @@ -212,33 +191,4 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input, return NvResult::Success; } -std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap( - GPUVAddr gpu_addr) const { - const auto it = std::find_if( - buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) { - return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr()); - }); - - ASSERT(it != buffer_mappings.end()); - return it->second; -} - -void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, - bool is_allocated) { - buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated}); -} - -std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) { - const auto iter{buffer_mappings.find(gpu_addr)}; - if (iter == buffer_mappings.end()) { - return std::nullopt; - } - std::size_t size = 0; - if (iter->second.IsAllocated()) { - size = iter->second.Size(); - } - buffer_mappings.erase(iter); - return size; -} - } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h index da10f5f41..af59f00d2 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -23,45 +23,6 @@ public: ~nvhost_nvdec_common() override; protected: - class BufferMap final { - public: - constexpr BufferMap() = default; - - constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_) - : start_addr{start_addr_}, end_addr{start_addr_ + size_} {} - - constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_, - bool is_allocated_) - : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_}, - is_allocated{is_allocated_} {} - - constexpr VAddr StartAddr() const { - return start_addr; - } - - constexpr VAddr EndAddr() const { - return end_addr; - } - - constexpr std::size_t Size() const { - return end_addr - start_addr; - } - - constexpr VAddr CpuAddr() const { - return cpu_addr; - } - - constexpr bool IsAllocated() const { - return is_allocated; - } - - private: - GPUVAddr start_addr{}; - GPUVAddr end_addr{}; - VAddr cpu_addr{}; - bool is_allocated{}; - }; - struct IoctlSetNvmapFD { s32_le nvmap_fd{}; }; @@ -154,17 +115,11 @@ protected: NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output); - std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const; - void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); - std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr); - s32_le nvmap_fd{}; u32_le submit_timeout{}; std::shared_ptr<nvmap> nvmap_dev; SyncpointManager& syncpoint_manager; std::array<u32, MaxSyncPoints> device_syncpoints{}; - // This is expected to be ordered, therefore we must use a map, not unordered_map - std::map<GPUVAddr, BufferMap> buffer_mappings; }; }; // namespace Devices } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 59ddf6298..b4c3a6099 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -9,17 +9,20 @@ #include "core/core.h" #include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nvflinger/buffer_queue.h" namespace Service::NVFlinger { -BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_) - : id(id_), layer_id(layer_id_), buffer_wait_event{kernel} { - Kernel::KAutoObject::Create(std::addressof(buffer_wait_event)); - buffer_wait_event.Initialize("BufferQueue:WaitEvent"); +BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, + KernelHelpers::ServiceContext& service_context_) + : id(id_), layer_id(layer_id_), service_context{service_context_} { + buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); } -BufferQueue::~BufferQueue() = default; +BufferQueue::~BufferQueue() { + service_context.CloseEvent(buffer_wait_event); +} void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { ASSERT(slot < buffer_slots); @@ -41,7 +44,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) .multi_fence = {}, }; - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, @@ -119,7 +122,7 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult } free_buffers_condition.notify_one(); - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() { @@ -154,7 +157,7 @@ void BufferQueue::ReleaseBuffer(u32 slot) { } free_buffers_condition.notify_one(); - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } void BufferQueue::Connect() { @@ -169,7 +172,7 @@ void BufferQueue::Disconnect() { std::unique_lock lock{queue_sequence_mutex}; queue_sequence.clear(); } - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); is_connect = false; free_buffers_condition.notify_one(); } @@ -189,11 +192,11 @@ u32 BufferQueue::Query(QueryType type) { } Kernel::KWritableEvent& BufferQueue::GetWritableBufferWaitEvent() { - return buffer_wait_event.GetWritableEvent(); + return buffer_wait_event->GetWritableEvent(); } Kernel::KReadableEvent& BufferQueue::GetBufferWaitEvent() { - return buffer_wait_event.GetReadableEvent(); + return buffer_wait_event->GetReadableEvent(); } } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 61e337ac5..759247eb0 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -24,6 +24,10 @@ class KReadableEvent; class KWritableEvent; } // namespace Kernel +namespace Service::KernelHelpers { +class ServiceContext; +} // namespace Service::KernelHelpers + namespace Service::NVFlinger { constexpr u32 buffer_slots = 0x40; @@ -54,7 +58,8 @@ public: NativeWindowFormat = 2, }; - explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_); + explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, + KernelHelpers::ServiceContext& service_context_); ~BufferQueue(); enum class BufferTransformFlags : u32 { @@ -130,12 +135,14 @@ private: std::list<u32> free_buffers; std::array<Buffer, buffer_slots> buffers; std::list<u32> queue_sequence; - Kernel::KEvent buffer_wait_event; + Kernel::KEvent* buffer_wait_event{}; std::mutex free_buffers_mutex; std::condition_variable free_buffers_condition; std::mutex queue_sequence_mutex; + + KernelHelpers::ServiceContext& service_context; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 941748970..00bff8caf 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -61,12 +61,13 @@ void NVFlinger::SplitVSync() { } } -NVFlinger::NVFlinger(Core::System& system_) : system(system_) { - displays.emplace_back(0, "Default", system); - displays.emplace_back(1, "External", system); - displays.emplace_back(2, "Edid", system); - displays.emplace_back(3, "Internal", system); - displays.emplace_back(4, "Null", system); +NVFlinger::NVFlinger(Core::System& system_) + : system(system_), service_context(system_, "nvflinger") { + displays.emplace_back(0, "Default", service_context, system); + displays.emplace_back(1, "External", service_context, system); + displays.emplace_back(2, "Edid", service_context, system); + displays.emplace_back(3, "Internal", service_context, system); + displays.emplace_back(4, "Null", service_context, system); guard = std::make_shared<std::mutex>(); // Schedule the screen composition events @@ -146,7 +147,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { const u32 buffer_queue_id = next_buffer_queue_id++; buffer_queues.emplace_back( - std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id)); + std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context)); display.CreateLayer(layer_id, *buffer_queues.back()); } diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index d80fd07ef..6d84cafb4 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -15,6 +15,7 @@ #include <vector> #include "common/common_types.h" +#include "core/hle/service/kernel_helpers.h" namespace Common { class Event; @@ -135,6 +136,8 @@ private: std::unique_ptr<std::thread> vsync_thread; std::unique_ptr<Common::Event> wait_event; std::atomic<bool> is_running{}; + + KernelHelpers::ServiceContext service_context; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 522a604a5..8795eb6b7 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -12,7 +12,7 @@ namespace Service::Set { namespace { -constexpr std::array<LanguageCode, 17> available_language_codes = {{ +constexpr std::array<LanguageCode, 18> available_language_codes = {{ LanguageCode::JA, LanguageCode::EN_US, LanguageCode::FR, @@ -30,6 +30,7 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{ LanguageCode::ES_419, LanguageCode::ZH_HANS, LanguageCode::ZH_HANT, + LanguageCode::PT_BR, }}; enum class KeyboardLayout : u64 { @@ -50,7 +51,7 @@ enum class KeyboardLayout : u64 { ChineseTraditional = 14, }; -constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_layout{{ +constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{ {LanguageCode::JA, KeyboardLayout::Japanese}, {LanguageCode::EN_US, KeyboardLayout::EnglishUs}, {LanguageCode::FR, KeyboardLayout::French}, @@ -68,10 +69,11 @@ constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_la {LanguageCode::ES_419, KeyboardLayout::SpanishLatin}, {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified}, {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional}, + {LanguageCode::PT_BR, KeyboardLayout::Portuguese}, }}; -constexpr std::size_t pre4_0_0_max_entries = 15; -constexpr std::size_t post4_0_0_max_entries = 17; +constexpr std::size_t PRE_4_0_0_MAX_ENTRIES = 0xF; +constexpr std::size_t POST_4_0_0_MAX_ENTRIES = 0x40; constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625}; @@ -81,9 +83,10 @@ void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_la rb.Push(static_cast<u32>(num_language_codes)); } -void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_size) { +void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_entries) { const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode); - const std::size_t copy_amount = std::min(requested_amount, max_size); + const std::size_t max_amount = std::min(requested_amount, max_entries); + const std::size_t copy_amount = std::min(available_language_codes.size(), max_amount); const std::size_t copy_size = copy_amount * sizeof(LanguageCode); ctx.WriteBuffer(available_language_codes.data(), copy_size); @@ -118,7 +121,7 @@ LanguageCode GetLanguageCodeFromIndex(std::size_t index) { void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); - GetAvailableLanguageCodesImpl(ctx, pre4_0_0_max_entries); + GetAvailableLanguageCodesImpl(ctx, PRE_4_0_0_MAX_ENTRIES); } void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) { @@ -140,19 +143,19 @@ void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) { void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); - GetAvailableLanguageCodesImpl(ctx, post4_0_0_max_entries); + GetAvailableLanguageCodesImpl(ctx, POST_4_0_0_MAX_ENTRIES); } void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); - PushResponseLanguageCode(ctx, pre4_0_0_max_entries); + PushResponseLanguageCode(ctx, PRE_4_0_0_MAX_ENTRIES); } void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); - PushResponseLanguageCode(ctx, post4_0_0_max_entries); + PushResponseLanguageCode(ctx, POST_4_0_0_MAX_ENTRIES); } void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index d5bd7828d..acabebeaa 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -31,6 +31,7 @@ enum class LanguageCode : u64 { ES_419 = 0x00003931342D7365, ZH_HANS = 0x00736E61482D687A, ZH_HANT = 0x00746E61482D687A, + PT_BR = 0x00000052422D7470, }; LanguageCode GetLanguageCodeFromIndex(std::size_t idx); diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index 0dd342dbf..b7705c02a 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -12,18 +12,21 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/k_writable_event.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/vi/display/vi_display.h" #include "core/hle/service/vi/layer/vi_layer.h" namespace Service::VI { -Display::Display(u64 id, std::string name_, Core::System& system) - : display_id{id}, name{std::move(name_)}, vsync_event{system.Kernel()} { - Kernel::KAutoObject::Create(std::addressof(vsync_event)); - vsync_event.Initialize(fmt::format("Display VSync Event {}", id)); +Display::Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_, + Core::System& system_) + : display_id{id}, name{std::move(name_)}, service_context{service_context_} { + vsync_event = service_context.CreateEvent(fmt::format("Display VSync Event {}", id)); } -Display::~Display() = default; +Display::~Display() { + service_context.CloseEvent(vsync_event); +} Layer& Display::GetLayer(std::size_t index) { return *layers.at(index); @@ -34,11 +37,11 @@ const Layer& Display::GetLayer(std::size_t index) const { } Kernel::KReadableEvent& Display::GetVSyncEvent() { - return vsync_event.GetReadableEvent(); + return vsync_event->GetReadableEvent(); } void Display::SignalVSyncEvent() { - vsync_event.GetWritableEvent().Signal(); + vsync_event->GetWritableEvent().Signal(); } void Display::CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue) { diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 166f2a4cc..0979fc421 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -18,6 +18,9 @@ class KEvent; namespace Service::NVFlinger { class BufferQueue; } +namespace Service::KernelHelpers { +class ServiceContext; +} // namespace Service::KernelHelpers namespace Service::VI { @@ -31,10 +34,13 @@ class Display { public: /// Constructs a display with a given unique ID and name. /// - /// @param id The unique ID for this display. + /// @param id The unique ID for this display. + /// @param service_context_ The ServiceContext for the owning service. /// @param name_ The name for this display. + /// @param system_ The global system instance. /// - Display(u64 id, std::string name_, Core::System& system); + Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_, + Core::System& system_); ~Display(); /// Gets the unique ID assigned to this display. @@ -98,9 +104,10 @@ public: private: u64 display_id; std::string name; + KernelHelpers::ServiceContext& service_context; std::vector<std::shared_ptr<Layer>> layers; - Kernel::KEvent vsync_event; + Kernel::KEvent* vsync_event{}; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 3e5949d52..8e8fc40ca 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -1158,7 +1158,7 @@ private: const auto layer_id = nv_flinger.CreateLayer(display_id); if (!layer_id) { - LOG_ERROR(Service_VI, "Layer not found! layer_id={}", *layer_id); + LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_NOT_FOUND); return; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f285c6f63..51c4dea26 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -4,8 +4,6 @@ #include <algorithm> #include <cstring> -#include <optional> -#include <utility> #include "common/assert.h" #include "common/atomic_ops.h" @@ -14,12 +12,10 @@ #include "common/page_table.h" #include "common/settings.h" #include "common/swap.h" -#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/device_memory.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/physical_memory.h" #include "core/memory.h" #include "video_core/gpu.h" @@ -62,17 +58,7 @@ struct Memory::Impl { } } - bool IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const { - const auto& page_table = process.PageTable().PageTableImpl(); - const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType(); - return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; - } - - bool IsValidVirtualAddress(VAddr vaddr) const { - return IsValidVirtualAddress(*system.CurrentProcess(), vaddr); - } - - u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const { + [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const { const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; if (!paddr) { @@ -82,18 +68,6 @@ struct Memory::Impl { return system.DeviceMemory().GetPointer(paddr) + vaddr; } - u8* GetPointer(const VAddr vaddr) const { - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); - if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - return pointer + vaddr; - } - const auto type = Common::PageTable::PageInfo::ExtractType(raw_pointer); - if (type == Common::PageType::RasterizerCachedMemory) { - return GetPointerFromRasterizerCachedMemory(vaddr); - } - return nullptr; - } - u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -179,7 +153,7 @@ struct Memory::Impl { std::string string; string.reserve(max_length); for (std::size_t i = 0; i < max_length; ++i) { - const char c = Read8(vaddr); + const char c = Read<s8>(vaddr); if (c == '\0') { break; } @@ -190,15 +164,14 @@ struct Memory::Impl { return string; } - void ReadBlock(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer, - const std::size_t size) { + void WalkBlock(const Kernel::KProcess& process, const VAddr addr, const std::size_t size, + auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) { const auto& page_table = process.PageTable().PageTableImpl(); - std::size_t remaining_size = size; - std::size_t page_index = src_addr >> PAGE_BITS; - std::size_t page_offset = src_addr & PAGE_MASK; + std::size_t page_index = addr >> PAGE_BITS; + std::size_t page_offset = addr & PAGE_MASK; - while (remaining_size > 0) { + while (remaining_size) { const std::size_t copy_amount = std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); @@ -206,22 +179,18 @@ struct Memory::Impl { const auto [pointer, type] = page_table.pointers[page_index].PointerType(); switch (type) { case Common::PageType::Unmapped: { - LOG_ERROR(HW_Memory, - "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - current_vaddr, src_addr, size); - std::memset(dest_buffer, 0, copy_amount); + on_unmapped(copy_amount, current_vaddr); break; } case Common::PageType::Memory: { DEBUG_ASSERT(pointer); - const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS); - std::memcpy(dest_buffer, src_ptr, copy_amount); + u8* mem_ptr = pointer + page_offset + (page_index << PAGE_BITS); + on_memory(copy_amount, mem_ptr); break; } case Common::PageType::RasterizerCachedMemory: { - const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; - system.GPU().FlushRegion(current_vaddr, copy_amount); - std::memcpy(dest_buffer, host_ptr, copy_amount); + u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + on_rasterizer(current_vaddr, copy_amount, host_ptr); break; } default: @@ -230,248 +199,122 @@ struct Memory::Impl { page_index++; page_offset = 0; - dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; + increment(copy_amount); remaining_size -= copy_amount; } } - void ReadBlockUnsafe(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer, - const std::size_t size) { - const auto& page_table = process.PageTable().PageTableImpl(); - - std::size_t remaining_size = size; - std::size_t page_index = src_addr >> PAGE_BITS; - std::size_t page_offset = src_addr & PAGE_MASK; - - while (remaining_size > 0) { - const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { + template <bool UNSAFE> + void ReadBlockImpl(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer, + const std::size_t size) { + WalkBlock( + process, src_addr, size, + [src_addr, size, &dest_buffer](const std::size_t copy_amount, + const VAddr current_vaddr) { LOG_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 Common::PageType::Memory: { - DEBUG_ASSERT(pointer); - const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS); + }, + [&dest_buffer](const std::size_t copy_amount, const u8* const src_ptr) { std::memcpy(dest_buffer, src_ptr, copy_amount); - break; - } - case Common::PageType::RasterizerCachedMemory: { - const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + }, + [&system = system, &dest_buffer](const VAddr current_vaddr, + const std::size_t copy_amount, + const u8* const host_ptr) { + if constexpr (!UNSAFE) { + system.GPU().FlushRegion(current_vaddr, copy_amount); + } std::memcpy(dest_buffer, host_ptr, copy_amount); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; - remaining_size -= copy_amount; - } + }, + [&dest_buffer](const std::size_t copy_amount) { + dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; + }); } void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { - ReadBlock(*system.CurrentProcess(), src_addr, dest_buffer, size); + ReadBlockImpl<false>(*system.CurrentProcess(), src_addr, dest_buffer, size); } void ReadBlockUnsafe(const VAddr src_addr, void* dest_buffer, const std::size_t size) { - ReadBlockUnsafe(*system.CurrentProcess(), src_addr, dest_buffer, size); + ReadBlockImpl<true>(*system.CurrentProcess(), src_addr, dest_buffer, size); } - void WriteBlock(const Kernel::KProcess& process, const VAddr dest_addr, const void* src_buffer, - const std::size_t size) { - const auto& page_table = process.PageTable().PageTableImpl(); - std::size_t remaining_size = size; - std::size_t page_index = dest_addr >> PAGE_BITS; - std::size_t page_offset = dest_addr & PAGE_MASK; - - while (remaining_size > 0) { - const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { + template <bool UNSAFE> + void WriteBlockImpl(const Kernel::KProcess& process, const VAddr dest_addr, + const void* src_buffer, const std::size_t size) { + WalkBlock( + process, dest_addr, size, + [dest_addr, size](const std::size_t copy_amount, const VAddr current_vaddr) { LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); - break; - } - case Common::PageType::Memory: { - DEBUG_ASSERT(pointer); - u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); + }, + [&src_buffer](const std::size_t copy_amount, u8* const dest_ptr) { std::memcpy(dest_ptr, src_buffer, copy_amount); - break; - } - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; - system.GPU().InvalidateRegion(current_vaddr, copy_amount); - std::memcpy(host_ptr, src_buffer, copy_amount); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - src_buffer = static_cast<const u8*>(src_buffer) + copy_amount; - remaining_size -= copy_amount; - } - } - - void WriteBlockUnsafe(const Kernel::KProcess& process, const VAddr dest_addr, - const void* src_buffer, const std::size_t size) { - const auto& page_table = process.PageTable().PageTableImpl(); - std::size_t remaining_size = size; - std::size_t page_index = dest_addr >> PAGE_BITS; - std::size_t page_offset = dest_addr & PAGE_MASK; - - while (remaining_size > 0) { - const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { - LOG_ERROR(HW_Memory, - "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - current_vaddr, dest_addr, size); - break; - } - case Common::PageType::Memory: { - DEBUG_ASSERT(pointer); - u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); - std::memcpy(dest_ptr, src_buffer, copy_amount); - break; - } - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + }, + [&system = system, &src_buffer](const VAddr current_vaddr, + const std::size_t copy_amount, u8* const host_ptr) { + if constexpr (!UNSAFE) { + system.GPU().InvalidateRegion(current_vaddr, copy_amount); + } std::memcpy(host_ptr, src_buffer, copy_amount); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - src_buffer = static_cast<const u8*>(src_buffer) + copy_amount; - remaining_size -= copy_amount; - } + }, + [&src_buffer](const std::size_t copy_amount) { + src_buffer = static_cast<const u8*>(src_buffer) + copy_amount; + }); } void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { - WriteBlock(*system.CurrentProcess(), dest_addr, src_buffer, size); + WriteBlockImpl<false>(*system.CurrentProcess(), dest_addr, src_buffer, size); } void WriteBlockUnsafe(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { - WriteBlockUnsafe(*system.CurrentProcess(), dest_addr, src_buffer, size); + WriteBlockImpl<true>(*system.CurrentProcess(), dest_addr, src_buffer, size); } void ZeroBlock(const Kernel::KProcess& process, const VAddr dest_addr, const std::size_t size) { - const auto& page_table = process.PageTable().PageTableImpl(); - std::size_t remaining_size = size; - std::size_t page_index = dest_addr >> PAGE_BITS; - std::size_t page_offset = dest_addr & PAGE_MASK; - - while (remaining_size > 0) { - const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { + WalkBlock( + process, dest_addr, size, + [dest_addr, size](const std::size_t copy_amount, const VAddr current_vaddr) { LOG_ERROR(HW_Memory, "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); - break; - } - case Common::PageType::Memory: { - DEBUG_ASSERT(pointer); - u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); + }, + [](const std::size_t copy_amount, u8* const dest_ptr) { std::memset(dest_ptr, 0, copy_amount); - break; - } - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + }, + [&system = system](const VAddr current_vaddr, const std::size_t copy_amount, + u8* const host_ptr) { system.GPU().InvalidateRegion(current_vaddr, copy_amount); std::memset(host_ptr, 0, copy_amount); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - remaining_size -= copy_amount; - } - } - - void ZeroBlock(const VAddr dest_addr, const std::size_t size) { - ZeroBlock(*system.CurrentProcess(), dest_addr, size); + }, + [](const std::size_t copy_amount) {}); } void CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr, const std::size_t size) { - const auto& page_table = process.PageTable().PageTableImpl(); - std::size_t remaining_size = size; - std::size_t page_index = src_addr >> PAGE_BITS; - std::size_t page_offset = src_addr & PAGE_MASK; - - while (remaining_size > 0) { - const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { + WalkBlock( + process, dest_addr, size, + [this, &process, &dest_addr, &src_addr, size](const std::size_t copy_amount, + const VAddr current_vaddr) { LOG_ERROR(HW_Memory, "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); ZeroBlock(process, dest_addr, copy_amount); - break; - } - case Common::PageType::Memory: { - DEBUG_ASSERT(pointer); - const u8* src_ptr = pointer + page_offset + (page_index << PAGE_BITS); - WriteBlock(process, dest_addr, src_ptr, copy_amount); - break; - } - case Common::PageType::RasterizerCachedMemory: { - const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + }, + [this, &process, &dest_addr](const std::size_t copy_amount, const u8* const src_ptr) { + WriteBlockImpl<false>(process, dest_addr, src_ptr, copy_amount); + }, + [this, &system = system, &process, &dest_addr]( + const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { system.GPU().FlushRegion(current_vaddr, copy_amount); - WriteBlock(process, dest_addr, host_ptr, copy_amount); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - dest_addr += static_cast<VAddr>(copy_amount); - src_addr += static_cast<VAddr>(copy_amount); - remaining_size -= copy_amount; - } - } - - void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { - return CopyBlock(*system.CurrentProcess(), dest_addr, src_addr, size); + WriteBlockImpl<false>(process, dest_addr, host_ptr, copy_amount); + }, + [&dest_addr, &src_addr](const std::size_t copy_amount) { + dest_addr += static_cast<VAddr>(copy_amount); + src_addr += static_cast<VAddr>(copy_amount); + }); } void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { @@ -514,7 +357,7 @@ struct Memory::Impl { } else { // Switch page type to uncached if now uncached switch (page_type) { - case Common::PageType::Unmapped: + case Common::PageType::Unmapped: // NOLINT(bugprone-branch-clone) // 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; @@ -597,52 +440,68 @@ struct Memory::Impl { } } - /** - * Reads a particular data type out of memory at the given virtual address. - * - * @param vaddr The virtual address to read the data type from. - * - * @tparam T The data type to read out of memory. This type *must* be - * trivially copyable, otherwise the behavior of this function - * is undefined. - * - * @returns The instance of T read from the specified virtual address. - */ - template <typename T> - T Read(VAddr vaddr) { + [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { // AARCH64 masks the upper 16 bit of all memory accesses vaddr &= 0xffffffffffffLL; if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { - LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); - return 0; + on_unmapped(); + return nullptr; } // Avoid adding any extra logic to this fast-path block const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); - if (const u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - T value; - std::memcpy(&value, &pointer[vaddr], sizeof(T)); - return value; + if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { + return &pointer[vaddr]; } switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { case Common::PageType::Unmapped: - LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); - return 0; + on_unmapped(); + return nullptr; case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); - break; + ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); + return nullptr; case Common::PageType::RasterizerCachedMemory: { - const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; - system.GPU().FlushRegion(vaddr, sizeof(T)); - T value; - std::memcpy(&value, host_ptr, sizeof(T)); - return value; + u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; + on_rasterizer(); + return host_ptr; } default: UNREACHABLE(); } - return {}; + return nullptr; + } + + [[nodiscard]] u8* GetPointer(const VAddr vaddr) const { + return GetPointerImpl( + vaddr, [vaddr]() { LOG_ERROR(HW_Memory, "Unmapped GetPointer @ 0x{:016X}", vaddr); }, + []() {}); + } + + /** + * Reads a particular data type out of memory at the given virtual address. + * + * @param vaddr The virtual address to read the data type from. + * + * @tparam T The data type to read out of memory. This type *must* be + * trivially copyable, otherwise the behavior of this function + * is undefined. + * + * @returns The instance of T read from the specified virtual address. + */ + template <typename T> + T Read(VAddr vaddr) { + T result = 0; + const u8* const ptr = GetPointerImpl( + vaddr, + [vaddr]() { + LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, vaddr); + }, + [&system = system, vaddr]() { system.GPU().FlushRegion(vaddr, sizeof(T)); }); + if (ptr) { + std::memcpy(&result, ptr, sizeof(T)); + } + return result; } /** @@ -656,110 +515,46 @@ struct Memory::Impl { */ template <typename T> void Write(VAddr vaddr, const T data) { - // AARCH64 masks the upper 16 bit of all memory accesses - vaddr &= 0xffffffffffffLL; - - if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return; - } - - // Avoid adding any extra logic to this fast-path block - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); - if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - std::memcpy(&pointer[vaddr], &data, sizeof(T)); - return; - } - switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { - case Common::PageType::Unmapped: - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return; - case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); - break; - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; - system.GPU().InvalidateRegion(vaddr, sizeof(T)); - std::memcpy(host_ptr, &data, sizeof(T)); - break; - } - default: - UNREACHABLE(); + u8* const ptr = GetPointerImpl( + vaddr, + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, + vaddr, static_cast<u64>(data)); + }, + [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); + if (ptr) { + std::memcpy(ptr, &data, sizeof(T)); } } template <typename T> bool WriteExclusive(VAddr vaddr, const T data, const T expected) { - // AARCH64 masks the upper 16 bit of all memory accesses - vaddr &= 0xffffffffffffLL; - - if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return true; - } - - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); - if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - // NOTE: Avoid adding any extra logic to this fast-path block - const auto volatile_pointer = reinterpret_cast<volatile T*>(&pointer[vaddr]); + u8* const ptr = GetPointerImpl( + vaddr, + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", + sizeof(T) * 8, vaddr, static_cast<u64>(data)); + }, + [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); + if (ptr) { + const auto volatile_pointer = reinterpret_cast<volatile T*>(ptr); return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); } - switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { - case Common::PageType::Unmapped: - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return true; - case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); - break; - case Common::PageType::RasterizerCachedMemory: { - u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; - system.GPU().InvalidateRegion(vaddr, sizeof(T)); - auto* pointer = reinterpret_cast<volatile T*>(&host_ptr); - return Common::AtomicCompareAndSwap(pointer, data, expected); - } - default: - UNREACHABLE(); - } return true; } bool WriteExclusive128(VAddr vaddr, const u128 data, const u128 expected) { - // AARCH64 masks the upper 16 bit of all memory accesses - vaddr &= 0xffffffffffffLL; - - if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, - static_cast<u32>(data[0]), vaddr); - return true; - } - - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); - if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - // NOTE: Avoid adding any extra logic to this fast-path block - const auto volatile_pointer = reinterpret_cast<volatile u64*>(&pointer[vaddr]); + u8* const ptr = GetPointerImpl( + vaddr, + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", + vaddr, static_cast<u64>(data[1]), static_cast<u64>(data[0])); + }, + [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(u128)); }); + if (ptr) { + const auto volatile_pointer = reinterpret_cast<volatile u64*>(ptr); return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); } - switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { - case Common::PageType::Unmapped: - LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}{:016X}", sizeof(data) * 8, - static_cast<u64>(data[1]), static_cast<u64>(data[0]), vaddr); - return true; - case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); - break; - case Common::PageType::RasterizerCachedMemory: { - u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; - system.GPU().InvalidateRegion(vaddr, sizeof(u128)); - auto* pointer = reinterpret_cast<volatile u64*>(&host_ptr); - return Common::AtomicCompareAndSwap(pointer, data, expected); - } - default: - UNREACHABLE(); - } return true; } @@ -789,12 +584,11 @@ void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { impl->UnmapRegion(page_table, base, size); } -bool Memory::IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const { - return impl->IsValidVirtualAddress(process, vaddr); -} - bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { - return impl->IsValidVirtualAddress(vaddr); + const Kernel::KProcess& process = *system.CurrentProcess(); + const auto& page_table = process.PageTable().PageTableImpl(); + const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType(); + return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; } u8* Memory::GetPointer(VAddr vaddr) { @@ -863,64 +657,38 @@ std::string Memory::ReadCString(VAddr vaddr, std::size_t max_length) { void Memory::ReadBlock(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer, const std::size_t size) { - impl->ReadBlock(process, src_addr, dest_buffer, size); + impl->ReadBlockImpl<false>(process, src_addr, dest_buffer, size); } void Memory::ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { impl->ReadBlock(src_addr, dest_buffer, size); } -void Memory::ReadBlockUnsafe(const Kernel::KProcess& process, const VAddr src_addr, - void* dest_buffer, const std::size_t size) { - impl->ReadBlockUnsafe(process, src_addr, dest_buffer, size); -} - void Memory::ReadBlockUnsafe(const VAddr src_addr, void* dest_buffer, const std::size_t size) { impl->ReadBlockUnsafe(src_addr, dest_buffer, size); } void Memory::WriteBlock(const Kernel::KProcess& process, VAddr dest_addr, const void* src_buffer, std::size_t size) { - impl->WriteBlock(process, dest_addr, src_buffer, size); + impl->WriteBlockImpl<false>(process, dest_addr, src_buffer, size); } void Memory::WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { impl->WriteBlock(dest_addr, src_buffer, size); } -void Memory::WriteBlockUnsafe(const Kernel::KProcess& process, VAddr dest_addr, - const void* src_buffer, std::size_t size) { - impl->WriteBlockUnsafe(process, dest_addr, src_buffer, size); -} - void Memory::WriteBlockUnsafe(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { impl->WriteBlockUnsafe(dest_addr, src_buffer, size); } -void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) { - impl->ZeroBlock(process, dest_addr, size); -} - -void Memory::ZeroBlock(VAddr dest_addr, std::size_t size) { - impl->ZeroBlock(dest_addr, size); -} - void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr, const std::size_t size) { impl->CopyBlock(process, dest_addr, src_addr, size); } -void Memory::CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { - impl->CopyBlock(dest_addr, src_addr, size); -} - void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { impl->RasterizerMarkRegionCached(vaddr, size, cached); } -bool IsKernelVirtualAddress(const VAddr vaddr) { - return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END; -} - } // namespace Core::Memory diff --git a/src/core/memory.h b/src/core/memory.h index c91eeced9..b5721b740 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -39,11 +39,6 @@ enum : VAddr { /// Application stack DEFAULT_STACK_SIZE = 0x100000, - - /// Kernel Virtual Address Range - KERNEL_REGION_VADDR = 0xFFFFFF8000000000, - KERNEL_REGION_SIZE = 0x7FFFE00000, - KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE, }; /// Central class that handles all memory operations and state. @@ -56,7 +51,7 @@ public: Memory& operator=(const Memory&) = delete; Memory(Memory&&) = default; - Memory& operator=(Memory&&) = default; + Memory& operator=(Memory&&) = delete; /** * Resets the state of the Memory system. @@ -92,24 +87,13 @@ public: /** * Checks whether or not the supplied address is a valid virtual - * address for the given process. - * - * @param process The emulated process to check the address against. - * @param vaddr The virtual address to check the validity of. - * - * @returns True if the given virtual address is valid, false otherwise. - */ - bool IsValidVirtualAddress(const Kernel::KProcess& process, VAddr vaddr) const; - - /** - * Checks whether or not the supplied address is a valid virtual * address for the current process. * * @param vaddr The virtual address to check the validity of. * * @returns True if the given virtual address is valid, false otherwise. */ - bool IsValidVirtualAddress(VAddr vaddr) const; + [[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const; /** * Gets a pointer to the given address. @@ -134,7 +118,7 @@ public: * @returns The pointer to the given address, if the address is valid. * If the address is not valid, nullptr will be returned. */ - const u8* GetPointer(VAddr vaddr) const; + [[nodiscard]] const u8* GetPointer(VAddr vaddr) const; template <typename T> const T* GetPointer(VAddr vaddr) const { @@ -328,27 +312,6 @@ public: std::size_t size); /** - * Reads a contiguous block of bytes from a specified process' address space. - * This unsafe version does not trigger GPU flushing. - * - * @param process The process to read the data from. - * @param src_addr The virtual address to begin reading from. - * @param dest_buffer The buffer to place the read bytes into. - * @param size The amount of data to read, in bytes. - * - * @note If a size of 0 is specified, then this function reads nothing and - * no attempts to access memory are made at all. - * - * @pre dest_buffer must be at least size bytes in length, otherwise a - * buffer overrun will occur. - * - * @post The range [dest_buffer, size) contains the read bytes from the - * process' address space. - */ - void ReadBlockUnsafe(const Kernel::KProcess& process, VAddr src_addr, void* dest_buffer, - std::size_t size); - - /** * Reads a contiguous block of bytes from the current process' address space. * * @param src_addr The virtual address to begin reading from. @@ -409,26 +372,6 @@ public: std::size_t size); /** - * Writes a range of bytes into a given process' address space at the specified - * virtual address. - * This unsafe version does not invalidate GPU Memory. - * - * @param process The process to write data into the address space of. - * @param dest_addr The destination virtual address to begin writing the data at. - * @param src_buffer The data to write into the process' address space. - * @param size The size of the data to write, in bytes. - * - * @post The address range [dest_addr, size) in the process' address space - * contains the data that was within src_buffer. - * - * @post If an attempt is made to write into an unmapped region of memory, the writes - * will be ignored and an error will be logged. - * - */ - void WriteBlockUnsafe(const Kernel::KProcess& process, VAddr dest_addr, const void* src_buffer, - std::size_t size); - - /** * Writes a range of bytes into the current process' address space at the specified * virtual address. * @@ -468,29 +411,6 @@ public: void WriteBlockUnsafe(VAddr dest_addr, const void* src_buffer, std::size_t size); /** - * Fills the specified address range within a process' address space with zeroes. - * - * @param process The process that will have a portion of its memory zeroed out. - * @param dest_addr The starting virtual address of the range to zero out. - * @param size The size of the address range to zero out, in bytes. - * - * @post The range [dest_addr, size) within the process' address space is - * filled with zeroes. - */ - void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); - - /** - * Fills the specified address range within the current process' address space with zeroes. - * - * @param dest_addr The starting virtual address of the range to zero out. - * @param size The size of the address range to zero out, in bytes. - * - * @post The range [dest_addr, size) within the current process' address space is - * filled with zeroes. - */ - void ZeroBlock(VAddr dest_addr, std::size_t size); - - /** * Copies data within a process' address space to another location within the * same address space. * @@ -506,19 +426,6 @@ public: std::size_t size); /** - * Copies data within the current process' address space to another location within the - * same address space. - * - * @param dest_addr The destination virtual address to begin copying the data into. - * @param src_addr The source virtual address to begin copying the data from. - * @param size The size of the data to copy, in bytes. - * - * @post The range [dest_addr, size) within the current process' address space - * contains the same data within the range [src_addr, size). - */ - void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size); - - /** * Marks each page within the specified address range as cached or uncached. * * @param vaddr The virtual address indicating the start of the address range. @@ -535,7 +442,4 @@ private: std::unique_ptr<Impl> impl; }; -/// Determines if the given VAddr is a kernel address -bool IsKernelVirtualAddress(VAddr vaddr); - } // namespace Core::Memory diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp index 526bfa110..4732d4485 100644 --- a/src/core/network/network.cpp +++ b/src/core/network/network.cpp @@ -10,9 +10,10 @@ #include "common/common_funcs.h" #ifdef _WIN32 -#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname #include <winsock2.h> +#include <ws2tcpip.h> #elif YUZU_UNIX +#include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <netdb.h> @@ -27,7 +28,9 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/settings.h" #include "core/network/network.h" +#include "core/network/network_interface.h" #include "core/network/sockets.h" namespace Network { @@ -47,11 +50,6 @@ void Finalize() { WSACleanup(); } -constexpr IPv4Address TranslateIPv4(in_addr addr) { - auto& bytes = addr.S_un.S_un_b; - return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4}; -} - sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -138,12 +136,6 @@ void Initialize() {} void Finalize() {} -constexpr IPv4Address TranslateIPv4(in_addr addr) { - const u32 bytes = addr.s_addr; - return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8), - static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)}; -} - sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -182,7 +174,7 @@ linger MakeLinger(bool enable, u32 linger_value) { } bool EnableNonBlock(int fd, bool enable) { - int flags = fcntl(fd, F_GETFD); + int flags = fcntl(fd, F_GETFL); if (flags == -1) { return false; } @@ -191,7 +183,7 @@ bool EnableNonBlock(int fd, bool enable) { } else { flags &= ~O_NONBLOCK; } - return fcntl(fd, F_SETFD, flags) == 0; + return fcntl(fd, F_SETFL, flags) == 0; } Errno TranslateNativeError(int e) { @@ -227,8 +219,12 @@ Errno GetAndLogLastError() { #else int e = errno; #endif + const Errno err = TranslateNativeError(e); + if (err == Errno::AGAIN) { + return err; + } LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e)); - return TranslateNativeError(e); + return err; } int TranslateDomain(Domain domain) { @@ -353,27 +349,29 @@ NetworkInstance::~NetworkInstance() { Finalize(); } -std::pair<IPv4Address, Errno> GetHostIPv4Address() { - std::array<char, 256> name{}; - if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) { - return {IPv4Address{}, GetAndLogLastError()}; +std::optional<IPv4Address> GetHostIPv4Address() { + const std::string& selected_network_interface = Settings::values.network_interface.GetValue(); + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + if (network_interfaces.size() == 0) { + LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); + return {}; } - hostent* const ent = gethostbyname(name.data()); - if (!ent) { - return {IPv4Address{}, GetAndLogLastError()}; - } - if (ent->h_addr_list == nullptr) { - UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list"); - return {IPv4Address{}, Errno::SUCCESS}; - } - if (ent->h_length != sizeof(in_addr)) { - UNIMPLEMENTED_MSG("Unexpected size={} in hostent->h_length", ent->h_length); - } + const auto res = + std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { + return iface.name == selected_network_interface; + }); + + if (res != network_interfaces.end()) { + char ip_addr[16] = {}; + ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr); + LOG_INFO(Network, "IP address: {}", ip_addr); - in_addr addr; - std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr)); - return {TranslateIPv4(addr), Errno::SUCCESS}; + return TranslateIPv4(res->ip_address); + } else { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + return {}; + } } std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { @@ -570,7 +568,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, ASSERT(flags == 0); const sockaddr* to = nullptr; - const int tolen = addr ? 0 : sizeof(sockaddr); + const int tolen = addr ? sizeof(sockaddr) : 0; sockaddr host_addr_in; if (addr) { diff --git a/src/core/network/network.h b/src/core/network/network.h index bd30f1899..fdd3e4655 100644 --- a/src/core/network/network.h +++ b/src/core/network/network.h @@ -5,11 +5,18 @@ #pragma once #include <array> +#include <optional> #include <utility> #include "common/common_funcs.h" #include "common/common_types.h" +#ifdef _WIN32 +#include <winsock2.h> +#elif YUZU_UNIX +#include <netinet/in.h> +#endif + namespace Network { class Socket; @@ -92,8 +99,21 @@ public: ~NetworkInstance(); }; +#ifdef _WIN32 +constexpr IPv4Address TranslateIPv4(in_addr addr) { + auto& bytes = addr.S_un.S_un_b; + return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4}; +} +#elif YUZU_UNIX +constexpr IPv4Address TranslateIPv4(in_addr addr) { + const u32 bytes = addr.s_addr; + return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8), + static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)}; +} +#endif + /// @brief Returns host's IPv4 address -/// @return Pair of an array of human ordered IPv4 address (e.g. 192.168.0.1) and an error code -std::pair<IPv4Address, Errno> GetHostIPv4Address(); +/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array +std::optional<IPv4Address> GetHostIPv4Address(); } // namespace Network diff --git a/src/core/network/network_interface.cpp b/src/core/network/network_interface.cpp new file mode 100644 index 000000000..cecc9aa11 --- /dev/null +++ b/src/core/network/network_interface.cpp @@ -0,0 +1,203 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <fstream> +#include <sstream> +#include <vector> + +#include "common/bit_cast.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/network/network_interface.h" + +#ifdef _WIN32 +#include <iphlpapi.h> +#else +#include <cerrno> +#include <ifaddrs.h> +#include <net/if.h> +#endif + +namespace Network { + +#ifdef _WIN32 + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { + std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses; + DWORD ret = ERROR_BUFFER_OVERFLOW; + DWORD buf_size = 0; + + // retry up to 5 times + for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) { + ret = GetAdaptersAddresses( + AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, + nullptr, adapter_addresses.data(), &buf_size); + + if (ret == ERROR_BUFFER_OVERFLOW) { + adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1); + } else { + break; + } + } + + if (ret == NO_ERROR) { + std::vector<NetworkInterface> result; + + for (auto current_address = adapter_addresses.data(); current_address != nullptr; + current_address = current_address->Next) { + if (current_address->FirstUnicastAddress == nullptr || + current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) { + continue; + } + + if (current_address->OperStatus != IfOperStatusUp) { + continue; + } + + const auto ip_addr = Common::BitCast<struct sockaddr_in>( + *current_address->FirstUnicastAddress->Address.lpSockaddr) + .sin_addr; + + ULONG mask = 0; + if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength, + &mask) != NO_ERROR) { + LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask"); + continue; + } + + struct in_addr gateway = {.S_un{.S_addr{0}}}; + if (current_address->FirstGatewayAddress != nullptr && + current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) { + gateway = Common::BitCast<struct sockaddr_in>( + *current_address->FirstGatewayAddress->Address.lpSockaddr) + .sin_addr; + } + + result.push_back(NetworkInterface{ + .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})}, + .ip_address{ip_addr}, + .subnet_mask = in_addr{.S_un{.S_addr{mask}}}, + .gateway = gateway}); + } + + return result; + } else { + LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses"); + return {}; + } +} + +#else + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { + std::vector<NetworkInterface> result; + + struct ifaddrs* ifaddr = nullptr; + + if (getifaddrs(&ifaddr) != 0) { + LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}", + std::strerror(errno)); + return result; + } + + for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) { + continue; + } + + if (ifa->ifa_addr->sa_family != AF_INET) { + continue; + } + + if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) { + continue; + } + + std::uint32_t gateway{0}; + std::ifstream file{"/proc/net/route"}; + if (file.is_open()) { + + // ignore header + file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + + bool gateway_found = false; + + for (std::string line; std::getline(file, line);) { + std::istringstream iss{line}; + + std::string iface_name{}; + iss >> iface_name; + if (iface_name != ifa->ifa_name) { + continue; + } + + iss >> std::hex; + + std::uint32_t dest{0}; + iss >> dest; + if (dest != 0) { + // not the default route + continue; + } + + iss >> gateway; + + std::uint16_t flags{0}; + iss >> flags; + + // flag RTF_GATEWAY (defined in <linux/route.h>) + if ((flags & 0x2) == 0) { + continue; + } + + gateway_found = true; + break; + } + + if (!gateway_found) { + gateway = 0; + } + } else { + LOG_ERROR(Network, "Failed to open \"/proc/net/route\""); + } + + result.push_back(NetworkInterface{ + .name{ifa->ifa_name}, + .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}, + .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr}, + .gateway{in_addr{.s_addr = gateway}}}); + } + + freeifaddrs(ifaddr); + + return result; +} + +#endif + +std::optional<NetworkInterface> GetSelectedNetworkInterface() { + const std::string& selected_network_interface = Settings::values.network_interface.GetValue(); + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + if (network_interfaces.size() == 0) { + LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); + return {}; + } + + const auto res = + std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { + return iface.name == selected_network_interface; + }); + + if (res != network_interfaces.end()) { + return *res; + } else { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + return {}; + } +} + +} // namespace Network diff --git a/src/core/network/network_interface.h b/src/core/network/network_interface.h new file mode 100644 index 000000000..980edb2f5 --- /dev/null +++ b/src/core/network/network_interface.h @@ -0,0 +1,29 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <string> +#include <vector> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#endif + +namespace Network { + +struct NetworkInterface { + std::string name; + struct in_addr ip_address; + struct in_addr subnet_mask; + struct in_addr gateway; +}; + +std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); +std::optional<NetworkInterface> GetSelectedNetworkInterface(); + +} // namespace Network diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 8de3d4520..ff23230f0 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -304,10 +304,10 @@ std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers([ } std::string GenerateKeyboardParam(int key_code) { - Common::ParamPackage param{ - {"engine", "keyboard"}, - {"code", std::to_string(key_code)}, - }; + Common::ParamPackage param; + param.Set("engine", "keyboard"); + param.Set("code", key_code); + param.Set("toggle", false); return param.Serialize(); } diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp index efcdd85d2..090b26972 100644 --- a/src/input_common/mouse/mouse_poller.cpp +++ b/src/input_common/mouse/mouse_poller.cpp @@ -57,6 +57,7 @@ Common::ParamPackage MouseButtonFactory::GetNextInput() const { if (pad.button != MouseInput::MouseButton::Undefined) { params.Set("engine", "mouse"); params.Set("button", static_cast<u16>(pad.button)); + params.Set("toggle", false); return params; } } diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 70a0ba09c..f102410d1 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -82,6 +82,12 @@ public: state.buttons.insert_or_assign(button, value); } + void PreSetButton(int button) { + if (!state.buttons.contains(button)) { + SetButton(button, false); + } + } + void SetMotion(SDL_ControllerSensorEvent event) { constexpr float gravity_constant = 9.80665f; std::lock_guard lock{mutex}; @@ -155,9 +161,16 @@ public: state.axes.insert_or_assign(axis, value); } - float GetAxis(int axis, float range) const { + void PreSetAxis(int axis) { + if (!state.axes.contains(axis)) { + SetAxis(axis, 0); + } + } + + float GetAxis(int axis, float range, float offset) const { std::lock_guard lock{mutex}; - return static_cast<float>(state.axes.at(axis)) / (32767.0f * range); + const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f; + return (value + offset) / range; } bool RumblePlay(u16 amp_low, u16 amp_high) { @@ -174,9 +187,10 @@ public: return false; } - std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { - float x = GetAxis(axis_x, range); - float y = GetAxis(axis_y, range); + std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x, + float offset_y) const { + float x = GetAxis(axis_x, range, offset_x); + float y = GetAxis(axis_y, range, offset_y); y = -y; // 3DS uses an y-axis inverse from SDL // Make sure the coordinates are in the unit circle, @@ -483,7 +497,7 @@ public: trigger_if_greater(trigger_if_greater_) {} bool GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f); + const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); if (trigger_if_greater) { return axis_value > threshold; } @@ -500,12 +514,14 @@ private: class SDLAnalog final : public Input::AnalogDevice { public: explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, - bool invert_x_, bool invert_y_, float deadzone_, float range_) + bool invert_x_, bool invert_y_, float deadzone_, float range_, + float offset_x_, float offset_y_) : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), - invert_y(invert_y_), deadzone(deadzone_), range(range_) {} + invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_), + offset_y(offset_y_) {} std::tuple<float, float> GetStatus() const override { - auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); + auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y); const float r = std::sqrt((x * x) + (y * y)); if (invert_x) { x = -x; @@ -522,8 +538,8 @@ public: } std::tuple<float, float> GetRawStatus() const override { - const float x = joystick->GetAxis(axis_x, range); - const float y = joystick->GetAxis(axis_y, range); + const float x = joystick->GetAxis(axis_x, range, offset_x); + const float y = joystick->GetAxis(axis_y, range, offset_y); return {x, -y}; } @@ -555,6 +571,8 @@ private: const bool invert_y; const float deadzone; const float range; + const float offset_x; + const float offset_y; }; class SDLVibration final : public Input::VibrationDevice { @@ -621,7 +639,7 @@ public: trigger_if_greater(trigger_if_greater_) {} Input::MotionStatus GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f); + const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); bool trigger = axis_value < threshold; if (trigger_if_greater) { trigger = axis_value > threshold; @@ -720,13 +738,13 @@ public: LOG_ERROR(Input, "Unknown direction {}", direction_name); } // This is necessary so accessing GetAxis with axis won't crash - joystick->SetAxis(axis, 0); + joystick->PreSetAxis(axis); return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); } const int button = params.Get("button", 0); // This is necessary so accessing GetButton with button won't crash - joystick->SetButton(button, false); + joystick->PreSetButton(button); return std::make_unique<SDLButton>(joystick, button, toggle); } @@ -757,13 +775,15 @@ public: const std::string invert_y_value = params.Get("invert_y", "+"); const bool invert_x = invert_x_value == "-"; const bool invert_y = invert_y_value == "-"; + const float offset_x = params.Get("offset_x", 0.0f); + const float offset_y = params.Get("offset_y", 0.0f); auto joystick = state.GetSDLJoystickByGUID(guid, port); // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->SetAxis(axis_x, 0); - joystick->SetAxis(axis_y, 0); + joystick->PreSetAxis(axis_x); + joystick->PreSetAxis(axis_y); return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone, - range); + range, offset_x, offset_y); } private: @@ -844,13 +864,13 @@ public: LOG_ERROR(Input, "Unknown direction {}", direction_name); } // This is necessary so accessing GetAxis with axis won't crash - joystick->SetAxis(axis, 0); + joystick->PreSetAxis(axis); return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater); } const int button = params.Get("button", 0); // This is necessary so accessing GetButton with button won't crash - joystick->SetButton(button, false); + joystick->PreSetButton(button); return std::make_unique<SDLButtonMotion>(joystick, button); } @@ -869,6 +889,9 @@ SDLState::SDLState() { RegisterFactory<VibrationDevice>("sdl", vibration_factory); RegisterFactory<MotionDevice>("sdl", motion_factory); + // Disable raw input. When enabled this setting causes SDL to die when a web applet opens + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); @@ -995,6 +1018,7 @@ Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid params.Set("port", port); params.Set("guid", std::move(guid)); params.Set("button", button); + params.Set("toggle", false); return params; } @@ -1134,13 +1158,15 @@ Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& gu } Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, - int axis_y) { + int axis_y, float offset_x, float offset_y) { Common::ParamPackage params; params.Set("engine", "sdl"); params.Set("port", port); params.Set("guid", guid); params.Set("axis_x", axis_x); params.Set("axis_y", axis_y); + params.Set("offset_x", offset_x); + params.Set("offset_y", offset_y); params.Set("invert_x", "+"); params.Set("invert_y", "+"); return params; @@ -1342,24 +1368,39 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa const auto& binding_left_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); if (params.Has("guid2")) { + joystick2->PreSetAxis(binding_left_x.value.axis); + joystick2->PreSetAxis(binding_left_y.value.axis); + const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0); + const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0); mapping.insert_or_assign( Settings::NativeAnalog::LStick, BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis)); + binding_left_x.value.axis, binding_left_y.value.axis, + left_offset_x, left_offset_y)); } else { + joystick->PreSetAxis(binding_left_x.value.axis); + joystick->PreSetAxis(binding_left_y.value.axis); + const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0); + const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0); mapping.insert_or_assign( Settings::NativeAnalog::LStick, BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis)); + binding_left_x.value.axis, binding_left_y.value.axis, + left_offset_x, left_offset_y)); } const auto& binding_right_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); const auto& binding_right_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + joystick->PreSetAxis(binding_right_x.value.axis); + joystick->PreSetAxis(binding_right_y.value.axis); + const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0); + const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0); mapping.insert_or_assign(Settings::NativeAnalog::RStick, BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), binding_right_x.value.axis, - binding_right_y.value.axis)); + binding_right_y.value.axis, right_offset_x, + right_offset_y)); return mapping; } @@ -1563,8 +1604,9 @@ public: } if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { + // Set offset to zero since the joystick is not on center auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - first_axis, axis); + first_axis, axis, 0, 0); first_axis = -1; return params; } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index fb8c02a77..14c77f162 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -298,14 +298,10 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) { if (IR::IsGeneric(attr)) { const u32 index{IR::GenericAttributeIndex(attr)}; const std::optional<AttrInfo> type{AttrTypes(ctx, index)}; - if (!type) { - // Attribute is disabled + if (!type || !ctx.runtime_info.previous_stage_stores.Generic(index, element)) { + // Attribute is disabled or varying component is not written return ctx.Const(element == 3 ? 1.0f : 0.0f); } - if (!ctx.runtime_info.previous_stage_stores.Generic(index, element)) { - // Varying component is not written - return ctx.Const(type && element == 3 ? 1.0f : 0.0f); - } const Id generic_id{ctx.input_generics.at(index)}; const Id pointer{AttrPointer(ctx, type->pointer, vertex, generic_id, ctx.Const(element))}; const Id value{ctx.OpLoad(type->id, pointer)}; diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h index 795194d41..334bb47aa 100644 --- a/src/shader_recompiler/frontend/ir/value.h +++ b/src/shader_recompiler/frontend/ir/value.h @@ -57,6 +57,7 @@ public: [[nodiscard]] IR::Inst* Inst() const; [[nodiscard]] IR::Inst* InstRecursive() const; + [[nodiscard]] IR::Inst* TryInstRecursive() const; [[nodiscard]] IR::Value Resolve() const; [[nodiscard]] IR::Reg Reg() const; [[nodiscard]] IR::Pred Pred() const; @@ -308,6 +309,13 @@ inline IR::Inst* Value::InstRecursive() const { return inst; } +inline IR::Inst* Value::TryInstRecursive() const { + if (IsIdentity()) { + return inst->Arg(0).TryInstRecursive(); + } + return type == Type::Opaque ? inst : nullptr; +} + inline IR::Value Value::Resolve() const { if (IsIdentity()) { return inst->Arg(0).Resolve(); diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 5ead930f1..f69e1c9cc 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -111,6 +111,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ConvertF16U16: case IR::Opcode::ConvertF16U32: case IR::Opcode::ConvertF16U64: + case IR::Opcode::ConvertF16F32: + case IR::Opcode::ConvertF32F16: case IR::Opcode::FPAbs16: case IR::Opcode::FPAdd16: case IR::Opcode::FPCeil16: diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp index 8dd6d6c2c..d089fdd12 100644 --- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <functional> #include <tuple> #include <type_traits> @@ -88,6 +89,26 @@ bool FoldWhenAllImmediates(IR::Inst& inst, Func&& func) { return true; } +/// Return true when all values in a range are equal +template <typename Range> +bool AreEqual(const Range& range) { + auto resolver{[](const auto& value) { return value.Resolve(); }}; + auto equal{[](const IR::Value& lhs, const IR::Value& rhs) { + if (lhs == rhs) { + return true; + } + // Not equal, but try to match if they read the same constant buffer + if (!lhs.IsImmediate() && !rhs.IsImmediate() && + lhs.Inst()->GetOpcode() == IR::Opcode::GetCbufU32 && + rhs.Inst()->GetOpcode() == IR::Opcode::GetCbufU32 && + lhs.Inst()->Arg(0) == rhs.Inst()->Arg(0) && lhs.Inst()->Arg(1) == rhs.Inst()->Arg(1)) { + return true; + } + return false; + }}; + return std::ranges::adjacent_find(range, std::not_fn(equal), resolver) == std::end(range); +} + void FoldGetRegister(IR::Inst& inst) { if (inst.Arg(0).Reg() == IR::Reg::RZ) { inst.ReplaceUsesWith(IR::Value{u32{0}}); @@ -100,6 +121,157 @@ void FoldGetPred(IR::Inst& inst) { } } +/// Replaces the XMAD pattern generated by an integer FMA +bool FoldXmadMultiplyAdd(IR::Block& block, IR::Inst& inst) { + /* + * We are looking for this specific pattern: + * %6 = BitFieldUExtract %op_b, #0, #16 + * %7 = BitFieldUExtract %op_a', #16, #16 + * %8 = IMul32 %6, %7 + * %10 = BitFieldUExtract %op_a', #0, #16 + * %11 = BitFieldInsert %8, %10, #16, #16 + * %15 = BitFieldUExtract %op_b, #0, #16 + * %16 = BitFieldUExtract %op_a, #0, #16 + * %17 = IMul32 %15, %16 + * %18 = IAdd32 %17, %op_c + * %22 = BitFieldUExtract %op_b, #16, #16 + * %23 = BitFieldUExtract %11, #16, #16 + * %24 = IMul32 %22, %23 + * %25 = ShiftLeftLogical32 %24, #16 + * %26 = ShiftLeftLogical32 %11, #16 + * %27 = IAdd32 %26, %18 + * %result = IAdd32 %25, %27 + * + * And replace it with: + * %temp = IMul32 %op_a, %op_b + * %result = IAdd32 %temp, %op_c + * + * This optimization has been proven safe by Nvidia's compiler logic being reversed. + * (If Nvidia generates this code from 'fma(a, b, c)', we can do the same in the reverse order.) + */ + const IR::Value zero{0u}; + const IR::Value sixteen{16u}; + IR::Inst* const _25{inst.Arg(0).TryInstRecursive()}; + IR::Inst* const _27{inst.Arg(1).TryInstRecursive()}; + if (!_25 || !_27) { + return false; + } + if (_27->GetOpcode() != IR::Opcode::IAdd32) { + return false; + } + if (_25->GetOpcode() != IR::Opcode::ShiftLeftLogical32 || _25->Arg(1) != sixteen) { + return false; + } + IR::Inst* const _24{_25->Arg(0).TryInstRecursive()}; + if (!_24 || _24->GetOpcode() != IR::Opcode::IMul32) { + return false; + } + IR::Inst* const _22{_24->Arg(0).TryInstRecursive()}; + IR::Inst* const _23{_24->Arg(1).TryInstRecursive()}; + if (!_22 || !_23) { + return false; + } + if (_22->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_23->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_22->Arg(1) != sixteen || _22->Arg(2) != sixteen) { + return false; + } + if (_23->Arg(1) != sixteen || _23->Arg(2) != sixteen) { + return false; + } + IR::Inst* const _11{_23->Arg(0).TryInstRecursive()}; + if (!_11 || _11->GetOpcode() != IR::Opcode::BitFieldInsert) { + return false; + } + if (_11->Arg(2) != sixteen || _11->Arg(3) != sixteen) { + return false; + } + IR::Inst* const _8{_11->Arg(0).TryInstRecursive()}; + IR::Inst* const _10{_11->Arg(1).TryInstRecursive()}; + if (!_8 || !_10) { + return false; + } + if (_8->GetOpcode() != IR::Opcode::IMul32) { + return false; + } + if (_10->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + IR::Inst* const _6{_8->Arg(0).TryInstRecursive()}; + IR::Inst* const _7{_8->Arg(1).TryInstRecursive()}; + if (!_6 || !_7) { + return false; + } + if (_6->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_7->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_6->Arg(1) != zero || _6->Arg(2) != sixteen) { + return false; + } + if (_7->Arg(1) != sixteen || _7->Arg(2) != sixteen) { + return false; + } + IR::Inst* const _26{_27->Arg(0).TryInstRecursive()}; + IR::Inst* const _18{_27->Arg(1).TryInstRecursive()}; + if (!_26 || !_18) { + return false; + } + if (_26->GetOpcode() != IR::Opcode::ShiftLeftLogical32 || _26->Arg(1) != sixteen) { + return false; + } + if (_26->Arg(0).InstRecursive() != _11) { + return false; + } + if (_18->GetOpcode() != IR::Opcode::IAdd32) { + return false; + } + IR::Inst* const _17{_18->Arg(0).TryInstRecursive()}; + if (!_17 || _17->GetOpcode() != IR::Opcode::IMul32) { + return false; + } + IR::Inst* const _15{_17->Arg(0).TryInstRecursive()}; + IR::Inst* const _16{_17->Arg(1).TryInstRecursive()}; + if (!_15 || !_16) { + return false; + } + if (_15->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_16->GetOpcode() != IR::Opcode::BitFieldUExtract) { + return false; + } + if (_15->Arg(1) != zero || _16->Arg(1) != zero || _10->Arg(1) != zero) { + return false; + } + if (_15->Arg(2) != sixteen || _16->Arg(2) != sixteen || _10->Arg(2) != sixteen) { + return false; + } + const std::array<IR::Value, 3> op_as{ + _7->Arg(0).Resolve(), + _16->Arg(0).Resolve(), + _10->Arg(0).Resolve(), + }; + const std::array<IR::Value, 3> op_bs{ + _22->Arg(0).Resolve(), + _6->Arg(0).Resolve(), + _15->Arg(0).Resolve(), + }; + const IR::U32 op_c{_18->Arg(1)}; + if (!AreEqual(op_as) || !AreEqual(op_bs)) { + return false; + } + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + inst.ReplaceUsesWith(ir.IAdd(ir.IMul(IR::U32{op_as[0]}, IR::U32{op_bs[1]}), op_c)); + return true; +} + /// Replaces the pattern generated by two XMAD multiplications bool FoldXmadMultiply(IR::Block& block, IR::Inst& inst) { /* @@ -116,33 +288,31 @@ bool FoldXmadMultiply(IR::Block& block, IR::Inst& inst) { * * This optimization has been proven safe by LLVM and MSVC. */ - const IR::Value lhs_arg{inst.Arg(0)}; - const IR::Value rhs_arg{inst.Arg(1)}; - if (lhs_arg.IsImmediate() || rhs_arg.IsImmediate()) { + IR::Inst* const lhs_shl{inst.Arg(0).TryInstRecursive()}; + IR::Inst* const rhs_mul{inst.Arg(1).TryInstRecursive()}; + if (!lhs_shl || !rhs_mul) { return false; } - IR::Inst* const lhs_shl{lhs_arg.InstRecursive()}; if (lhs_shl->GetOpcode() != IR::Opcode::ShiftLeftLogical32 || lhs_shl->Arg(1) != IR::Value{16U}) { return false; } - if (lhs_shl->Arg(0).IsImmediate()) { + IR::Inst* const lhs_mul{lhs_shl->Arg(0).TryInstRecursive()}; + if (!lhs_mul) { return false; } - IR::Inst* const lhs_mul{lhs_shl->Arg(0).InstRecursive()}; - IR::Inst* const rhs_mul{rhs_arg.InstRecursive()}; if (lhs_mul->GetOpcode() != IR::Opcode::IMul32 || rhs_mul->GetOpcode() != IR::Opcode::IMul32) { return false; } - if (lhs_mul->Arg(1).Resolve() != rhs_mul->Arg(1).Resolve()) { + const IR::U32 factor_b{lhs_mul->Arg(1)}; + if (factor_b.Resolve() != rhs_mul->Arg(1).Resolve()) { return false; } - const IR::U32 factor_b{lhs_mul->Arg(1)}; - if (lhs_mul->Arg(0).IsImmediate() || rhs_mul->Arg(0).IsImmediate()) { + IR::Inst* const lhs_bfe{lhs_mul->Arg(0).TryInstRecursive()}; + IR::Inst* const rhs_bfe{rhs_mul->Arg(0).TryInstRecursive()}; + if (!lhs_bfe || !rhs_bfe) { return false; } - IR::Inst* const lhs_bfe{lhs_mul->Arg(0).InstRecursive()}; - IR::Inst* const rhs_bfe{rhs_mul->Arg(0).InstRecursive()}; if (lhs_bfe->GetOpcode() != IR::Opcode::BitFieldUExtract) { return false; } @@ -155,10 +325,10 @@ bool FoldXmadMultiply(IR::Block& block, IR::Inst& inst) { if (rhs_bfe->Arg(1) != IR::Value{0U} || rhs_bfe->Arg(2) != IR::Value{16U}) { return false; } - if (lhs_bfe->Arg(0).Resolve() != rhs_bfe->Arg(0).Resolve()) { + const IR::U32 factor_a{lhs_bfe->Arg(0)}; + if (factor_a.Resolve() != rhs_bfe->Arg(0).Resolve()) { return false; } - const IR::U32 factor_a{lhs_bfe->Arg(0)}; IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; inst.ReplaceUsesWith(ir.IMul(factor_a, factor_b)); return true; @@ -181,6 +351,9 @@ void FoldAdd(IR::Block& block, IR::Inst& inst) { if (FoldXmadMultiply(block, inst)) { return; } + if (FoldXmadMultiplyAdd(block, inst)) { + return; + } } } @@ -476,6 +649,10 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { return FoldInverseFunc(inst, IR::Opcode::UnpackHalf2x16); case IR::Opcode::UnpackHalf2x16: return FoldInverseFunc(inst, IR::Opcode::PackHalf2x16); + case IR::Opcode::PackFloat2x16: + return FoldInverseFunc(inst, IR::Opcode::UnpackFloat2x16); + case IR::Opcode::UnpackFloat2x16: + return FoldInverseFunc(inst, IR::Opcode::PackFloat2x16); case IR::Opcode::SelectU1: case IR::Opcode::SelectU8: case IR::Opcode::SelectU16: diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp index 4c0f9654f..e31ca3544 100644 --- a/src/tests/common/param_package.cpp +++ b/src/tests/common/param_package.cpp @@ -4,11 +4,13 @@ #include <catch2/catch.hpp> #include <math.h> +#include "common/logging/backend.h" #include "common/param_package.h" namespace Common { TEST_CASE("ParamPackage", "[common]") { + Common::Log::DisableLoggingInTests(); ParamPackage original{ {"abc", "xyz"}, {"def", "42"}, diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 007ecc13e..2f6cdd216 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,5 +1,10 @@ add_subdirectory(host_shaders) +if(LIBVA_FOUND) + set_source_files_properties(command_classes/codecs/codec.cpp + PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1) +endif() + add_library(video_core STATIC buffer_cache/buffer_base.h buffer_cache/buffer_cache.cpp @@ -92,6 +97,7 @@ add_library(video_core STATIC renderer_opengl/gl_stream_buffer.h renderer_opengl/gl_texture_cache.cpp renderer_opengl/gl_texture_cache.h + renderer_opengl/gl_texture_cache_base.cpp renderer_opengl/gl_query_cache.cpp renderer_opengl/gl_query_cache.h renderer_opengl/maxwell_to_gl.h @@ -106,6 +112,8 @@ add_library(video_core STATIC renderer_vulkan/maxwell_to_vk.cpp renderer_vulkan/maxwell_to_vk.h renderer_vulkan/pipeline_helper.h + renderer_vulkan/pipeline_statistics.cpp + renderer_vulkan/pipeline_statistics.h renderer_vulkan/renderer_vulkan.h renderer_vulkan/renderer_vulkan.cpp renderer_vulkan/vk_blit_screen.cpp @@ -148,6 +156,7 @@ add_library(video_core STATIC renderer_vulkan/vk_swapchain.h renderer_vulkan/vk_texture_cache.cpp renderer_vulkan/vk_texture_cache.h + renderer_vulkan/vk_texture_cache_base.cpp renderer_vulkan/vk_update_descriptor.cpp renderer_vulkan/vk_update_descriptor.h shader_cache.cpp @@ -179,6 +188,7 @@ add_library(video_core STATIC texture_cache/samples_helper.h texture_cache/slot_vector.h texture_cache/texture_cache.h + texture_cache/texture_cache_base.h texture_cache/types.h texture_cache/util.cpp texture_cache/util.h diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp index 1b4bbc8ac..f798a0053 100644 --- a/src/video_core/command_classes/codecs/codec.cpp +++ b/src/video_core/command_classes/codecs/codec.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> #include <fstream> #include <vector> #include "common/assert.h" @@ -17,10 +16,47 @@ extern "C" { } namespace Tegra { +#if defined(LIBVA_FOUND) +// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license +namespace { +constexpr std::array<const char*, 2> VAAPI_DRIVERS = { + "i915", + "amdgpu", +}; + +AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) { + for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { + if (*p == AV_PIX_FMT_VAAPI) { + return AV_PIX_FMT_VAAPI; + } + } + LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); + return *pix_fmts; +} + +bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) { + AVDictionary* hwdevice_options = nullptr; + av_dict_set(&hwdevice_options, "connection_type", "drm", 0); + for (const auto& driver : VAAPI_DRIVERS) { + av_dict_set(&hwdevice_options, "kernel_driver", driver, 0); + const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI, + nullptr, hwdevice_options, 0); + if (hwdevice_error >= 0) { + LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver); + av_dict_free(&hwdevice_options); + return true; + } + LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error); + } + LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers"); + av_dict_free(&hwdevice_options); + return false; +} +} // namespace +#endif void AVFrameDeleter(AVFrame* ptr) { - av_frame_unref(ptr); - av_free(ptr); + av_frame_free(&ptr); } Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs) @@ -32,19 +68,31 @@ Codec::~Codec() { return; } // Free libav memory - AVFrame* av_frame{nullptr}; avcodec_send_packet(av_codec_ctx, nullptr); - av_frame = av_frame_alloc(); + AVFrame* av_frame = av_frame_alloc(); avcodec_receive_frame(av_codec_ctx, av_frame); avcodec_flush_buffers(av_codec_ctx); - - av_frame_unref(av_frame); - av_free(av_frame); + av_frame_free(&av_frame); avcodec_close(av_codec_ctx); + av_buffer_unref(&av_hw_device); +} + +void Codec::InitializeHwdec() { + // Prioritize integrated GPU to mitigate bandwidth bottlenecks +#if defined(LIBVA_FOUND) + if (CreateVaapiHwdevice(&av_hw_device)) { + const auto hw_device_ctx = av_buffer_ref(av_hw_device); + ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); + av_codec_ctx->hw_device_ctx = hw_device_ctx; + av_codec_ctx->get_format = GetHwFormat; + return; + } +#endif + // TODO more GPU accelerated decoders } void Codec::Initialize() { - AVCodecID codec{AV_CODEC_ID_NONE}; + AVCodecID codec; switch (current_codec) { case NvdecCommon::VideoCodec::H264: codec = AV_CODEC_ID_H264; @@ -53,22 +101,24 @@ void Codec::Initialize() { codec = AV_CODEC_ID_VP9; break; default: + UNIMPLEMENTED_MSG("Unknown codec {}", current_codec); return; } av_codec = avcodec_find_decoder(codec); av_codec_ctx = avcodec_alloc_context3(av_codec); av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); - - // TODO(ameerj): libavcodec gpu hw acceleration - + InitializeHwdec(); + if (!av_codec_ctx->hw_device_ctx) { + LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); + } const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); if (av_error < 0) { LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); avcodec_close(av_codec_ctx); + av_buffer_unref(&av_hw_device); return; } initialized = true; - return; } void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { @@ -80,36 +130,64 @@ void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { void Codec::Decode() { const bool is_first_frame = !initialized; - if (!initialized) { + if (is_first_frame) { Initialize(); } - bool vp9_hidden_frame = false; - AVPacket packet{}; - av_init_packet(&packet); std::vector<u8> frame_data; - if (current_codec == NvdecCommon::VideoCodec::H264) { frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame); } else if (current_codec == NvdecCommon::VideoCodec::Vp9) { frame_data = vp9_decoder->ComposeFrameHeader(state); vp9_hidden_frame = vp9_decoder->WasFrameHidden(); } - + AVPacket packet{}; + av_init_packet(&packet); packet.data = frame_data.data(); packet.size = static_cast<s32>(frame_data.size()); - - avcodec_send_packet(av_codec_ctx, &packet); - - if (!vp9_hidden_frame) { - // Only receive/store visible frames - AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; - avcodec_receive_frame(av_codec_ctx, frame.get()); - av_frames.push(std::move(frame)); - // Limit queue to 10 frames. Workaround for ZLA decode and queue spam - if (av_frames.size() > 10) { - av_frames.pop(); - } + if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) { + LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret); + return; + } + // Only receive/store visible frames + if (vp9_hidden_frame) { + return; + } + AVFrame* hw_frame = av_frame_alloc(); + AVFrame* sw_frame = hw_frame; + ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed"); + if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) { + LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); + av_frame_free(&hw_frame); + return; + } + if (!hw_frame->width || !hw_frame->height) { + LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); + av_frame_free(&hw_frame); + return; + } +#if defined(LIBVA_FOUND) + // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license + if (hw_frame->format == AV_PIX_FMT_VAAPI) { + sw_frame = av_frame_alloc(); + ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed"); + // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp + // because Intel drivers crash unless using AV_PIX_FMT_NV12 + sw_frame->format = AV_PIX_FMT_NV12; + const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0); + ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret); + av_frame_free(&hw_frame); + } +#endif + if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) { + UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format); + av_frame_free(&sw_frame); + return; + } + av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); + if (av_frames.size() > 10) { + LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); + av_frames.pop(); } } @@ -119,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() { if (av_frames.empty()) { return AVFramePtr{nullptr, AVFrameDeleter}; } - AVFramePtr frame = std::move(av_frames.front()); av_frames.pop(); return frame; @@ -144,6 +221,5 @@ std::string_view Codec::GetCurrentCodecName() const { default: return "Unknown"; } -}; - +} } // namespace Tegra diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h index 96c823c76..71936203f 100644 --- a/src/video_core/command_classes/codecs/codec.h +++ b/src/video_core/command_classes/codecs/codec.h @@ -22,7 +22,6 @@ extern "C" { namespace Tegra { class GPU; -struct VicRegisters; void AVFrameDeleter(AVFrame* ptr); using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>; @@ -55,10 +54,13 @@ public: [[nodiscard]] std::string_view GetCurrentCodecName() const; private: + void InitializeHwdec(); + bool initialized{}; NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; AVCodec* av_codec{nullptr}; + AVBufferRef* av_hw_device{nullptr}; AVCodecContext* av_codec_ctx{nullptr}; GPU& gpu; diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp index 902bc2a98..70030066a 100644 --- a/src/video_core/command_classes/codecs/vp9.cpp +++ b/src/video_core/command_classes/codecs/vp9.cpp @@ -11,6 +11,9 @@ namespace Tegra::Decoder { namespace { +constexpr u32 diff_update_probability = 252; +constexpr u32 frame_sync_code = 0x498342; + // Default compressed header probabilities once frame context resets constexpr Vp9EntropyProbs default_probs{ .y_mode_prob{ @@ -361,8 +364,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy); // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following - // order: last, golden, altref, current. It may be worthwhile to track the updates done here - // to avoid buffering frame data needed for reference frame updating in the header composition. + // order: last, golden, altref, current. std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4, vp9_info.frame_offsets.begin()); @@ -384,40 +386,25 @@ Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(), current_frame.info.bitstream_size); } - // Buffer two frames, saving the last show frame info - if (!next_next_frame.bit_stream.empty()) { + if (!next_frame.bit_stream.empty()) { Vp9FrameContainer temp{ .info = current_frame.info, .bit_stream = std::move(current_frame.bit_stream), }; - next_next_frame.info.show_frame = current_frame.info.last_frame_shown; - current_frame.info = next_next_frame.info; - current_frame.bit_stream = std::move(next_next_frame.bit_stream); - next_next_frame = std::move(temp); - - if (!next_frame.bit_stream.empty()) { - Vp9FrameContainer temp2{ - .info = current_frame.info, - .bit_stream = std::move(current_frame.bit_stream), - }; - next_frame.info.show_frame = current_frame.info.last_frame_shown; - current_frame.info = next_frame.info; - current_frame.bit_stream = std::move(next_frame.bit_stream); - next_frame = std::move(temp2); - } else { - next_frame.info = current_frame.info; - next_frame.bit_stream = std::move(current_frame.bit_stream); - } + next_frame.info.show_frame = current_frame.info.last_frame_shown; + current_frame.info = next_frame.info; + current_frame.bit_stream = std::move(next_frame.bit_stream); + next_frame = std::move(temp); } else { - next_next_frame.info = current_frame.info; - next_next_frame.bit_stream = std::move(current_frame.bit_stream); + next_frame.info = current_frame.info; + next_frame.bit_stream = current_frame.bit_stream; } return current_frame; } std::vector<u8> VP9::ComposeCompressedHeader() { VpxRangeEncoder writer{}; - const bool update_probs = current_frame_info.show_frame && !current_frame_info.is_key_frame; + const bool update_probs = !current_frame_info.is_key_frame && current_frame_info.show_frame; if (!current_frame_info.lossless) { if (static_cast<u32>(current_frame_info.transform_mode) >= 3) { writer.Write(3, 2); @@ -613,86 +600,64 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() { // Reset context prev_frame_probs = default_probs; - swap_next_golden = false; + swap_ref_indices = false; loop_filter_ref_deltas.fill(0); loop_filter_mode_deltas.fill(0); - - // allow frames offsets to stabilize before checking for golden frames - grace_period = 4; - - // On key frames, all frame slots are set to the current frame, - // so the value of the selected slot doesn't really matter. - frame_ctxs.fill({current_frame_number, false, default_probs}); + frame_ctxs.fill(default_probs); // intra only, meaning the frame can be recreated with no other references current_frame_info.intra_only = true; - } else { - if (!current_frame_info.show_frame) { uncomp_writer.WriteBit(current_frame_info.intra_only); - if (!current_frame_info.last_frame_was_key) { - swap_next_golden = !swap_next_golden; - } } else { current_frame_info.intra_only = false; } if (!current_frame_info.error_resilient_mode) { uncomp_writer.WriteU(0, 2); // Reset frame context. } - - // Last, Golden, Altref frames - std::array<s32, 3> ref_frame_index{0, 1, 2}; - - // Set when next frame is hidden - // altref and golden references are swapped - if (swap_next_golden) { - ref_frame_index = std::array<s32, 3>{0, 2, 1}; + const auto& curr_offsets = current_frame_info.frame_offsets; + const auto& next_offsets = next_frame.info.frame_offsets; + const bool ref_frames_different = curr_offsets[1] != curr_offsets[2]; + const bool next_references_swap = + (next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]); + const bool needs_ref_swap = ref_frames_different && next_references_swap; + if (needs_ref_swap) { + swap_ref_indices = !swap_ref_indices; } - - // update Last Frame - u64 refresh_frame_flags = 1; - - // golden frame may refresh, determined if the next golden frame offset is changed - bool golden_refresh = false; - if (grace_period <= 0) { - for (s32 index = 1; index < 3; ++index) { - if (current_frame_info.frame_offsets[index] != - next_frame.info.frame_offsets[index]) { - current_frame_info.refresh_frame[index] = true; - golden_refresh = true; - grace_period = 3; - } + union { + u32 raw; + BitField<0, 1, u32> refresh_last; + BitField<1, 2, u32> refresh_golden; + BitField<2, 1, u32> refresh_alt; + } refresh_frame_flags; + + refresh_frame_flags.raw = 0; + for (u32 index = 0; index < 3; ++index) { + // Refresh indices that use the current frame as an index + if (curr_offsets[3] == next_offsets[index]) { + refresh_frame_flags.raw |= 1u << index; } } - - if (current_frame_info.show_frame && - (!next_frame.info.show_frame || next_frame.info.is_key_frame)) { - // Update golden frame - refresh_frame_flags = swap_next_golden ? 2 : 4; - } - - if (!current_frame_info.show_frame) { - // Update altref - refresh_frame_flags = swap_next_golden ? 2 : 4; - } else if (golden_refresh) { - refresh_frame_flags = 3; + if (swap_ref_indices) { + const u32 temp = refresh_frame_flags.refresh_golden; + refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value()); + refresh_frame_flags.refresh_alt.Assign(temp); } - if (current_frame_info.intra_only) { uncomp_writer.WriteU(frame_sync_code, 24); - uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); + uncomp_writer.WriteU(refresh_frame_flags.raw, 8); uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16); uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16); uncomp_writer.WriteBit(false); // Render and frame size different. } else { - uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); - - for (s32 index = 1; index < 4; index++) { + const bool swap_indices = needs_ref_swap ^ swap_ref_indices; + const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2}; + uncomp_writer.WriteU(refresh_frame_flags.raw, 8); + for (size_t index = 1; index < 4; index++) { uncomp_writer.WriteU(ref_frame_index[index - 1], 3); uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1); } - uncomp_writer.WriteBit(true); // Frame size with refs. uncomp_writer.WriteBit(false); // Render and frame size different. uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv); @@ -714,10 +679,9 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() { frame_ctx_idx = 1; } - uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. - prev_frame_probs = - frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header - frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy}; + uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. + prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header + frame_ctxs[frame_ctx_idx] = current_frame_info.entropy; uncomp_writer.WriteU(current_frame_info.first_level, 6); uncomp_writer.WriteU(current_frame_info.sharpness_level, 3); @@ -812,7 +776,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters current_frame_info = curr_frame.info; bitstream = std::move(curr_frame.bit_stream); } - // The uncompressed header routine sets PrevProb parameters needed for the compressed header auto uncomp_writer = ComposeUncompressedHeader(); std::vector<u8> compressed_header = ComposeCompressedHeader(); @@ -828,13 +791,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters frame.begin() + uncompressed_header.size()); std::copy(bitstream.begin(), bitstream.end(), frame.begin() + uncompressed_header.size() + compressed_header.size()); - - // keep track of frame number - current_frame_number++; - grace_period--; - - // don't display hidden frames - hidden = !current_frame_info.show_frame; return frame; } diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h index 8396c8105..e6e9fc17e 100644 --- a/src/video_core/command_classes/codecs/vp9.h +++ b/src/video_core/command_classes/codecs/vp9.h @@ -14,7 +14,6 @@ namespace Tegra { class GPU; -enum class FrameType { KeyFrame = 0, InterFrame = 1 }; namespace Decoder { /// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the @@ -124,7 +123,7 @@ public: /// Returns true if the most recent frame was a hidden frame. [[nodiscard]] bool WasFrameHidden() const { - return hidden; + return !current_frame_info.show_frame; } private: @@ -178,19 +177,12 @@ private: std::array<s8, 4> loop_filter_ref_deltas{}; std::array<s8, 2> loop_filter_mode_deltas{}; - bool hidden = false; - s64 current_frame_number = -2; // since we buffer 2 frames - s32 grace_period = 6; // frame offsets need to stabilize - std::array<FrameContexts, 4> frame_ctxs{}; Vp9FrameContainer next_frame{}; - Vp9FrameContainer next_next_frame{}; - bool swap_next_golden{}; + std::array<Vp9EntropyProbs, 4> frame_ctxs{}; + bool swap_ref_indices{}; Vp9PictureInfo current_frame_info{}; Vp9EntropyProbs prev_frame_probs{}; - - s32 diff_update_probability = 252; - s32 frame_sync_code = 0x498342; }; } // namespace Decoder diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h index 2da14f3ca..87eafdb03 100644 --- a/src/video_core/command_classes/codecs/vp9_types.h +++ b/src/video_core/command_classes/codecs/vp9_types.h @@ -176,7 +176,7 @@ struct PictureInfo { .frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0, .error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0, .last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0, - .show_frame = false, + .show_frame = true, .ref_frame_sign_bias = ref_frame_sign_bias, .base_q_index = base_q_index, .y_dc_delta_q = y_dc_delta_q, @@ -296,12 +296,6 @@ struct RefPoolElement { bool refresh{}; }; -struct FrameContexts { - s64 from; - bool adapted; - Vp9EntropyProbs probs; -}; - #define ASSERT_POSITION(field_name, position) \ static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \ "Field " #field_name " has invalid position") diff --git a/src/video_core/command_classes/nvdec.cpp b/src/video_core/command_classes/nvdec.cpp index b5e3b70fc..b5c55f14a 100644 --- a/src/video_core/command_classes/nvdec.cpp +++ b/src/video_core/command_classes/nvdec.cpp @@ -39,7 +39,7 @@ void Nvdec::Execute() { codec->Decode(); break; default: - UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec())); + UNIMPLEMENTED_MSG("Codec {}", codec->GetCurrentCodecName()); break; } } diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp index ffb7c82a1..0ee07f398 100644 --- a/src/video_core/command_classes/vic.cpp +++ b/src/video_core/command_classes/vic.cpp @@ -46,11 +46,8 @@ void Vic::ProcessMethod(Method method, u32 argument) { case Method::SetOutputSurfaceLumaOffset: output_surface_luma_address = arg; break; - case Method::SetOutputSurfaceChromaUOffset: - output_surface_chroma_u_address = arg; - break; - case Method::SetOutputSurfaceChromaVOffset: - output_surface_chroma_v_address = arg; + case Method::SetOutputSurfaceChromaOffset: + output_surface_chroma_address = arg; break; default: break; @@ -65,11 +62,10 @@ void Vic::Execute() { const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)}; const AVFramePtr frame_ptr = nvdec_processor->GetFrame(); const auto* frame = frame_ptr.get(); - if (!frame || frame->width == 0 || frame->height == 0) { + if (!frame) { return; } - const VideoPixelFormat pixel_format = - static_cast<VideoPixelFormat>(config.pixel_format.Value()); + const auto pixel_format = static_cast<VideoPixelFormat>(config.pixel_format.Value()); switch (pixel_format) { case VideoPixelFormat::BGRA8: case VideoPixelFormat::RGBA8: { @@ -83,37 +79,37 @@ void Vic::Execute() { sws_freeContext(scaler_ctx); scaler_ctx = nullptr; - // FFmpeg returns all frames in YUV420, convert it into expected format - scaler_ctx = - sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width, - frame->height, target_format, 0, nullptr, nullptr, nullptr); + // Frames are decoded into either YUV420 or NV12 formats. Convert to desired format + scaler_ctx = sws_getContext(frame->width, frame->height, + static_cast<AVPixelFormat>(frame->format), frame->width, + frame->height, target_format, 0, nullptr, nullptr, nullptr); scaler_width = frame->width; scaler_height = frame->height; } // Get Converted frame - const std::size_t linear_size = frame->width * frame->height * 4; + const u32 width = static_cast<u32>(frame->width); + const u32 height = static_cast<u32>(frame->height); + const std::size_t linear_size = width * height * 4; // Only allocate frame_buffer once per stream, as the size is not expected to change if (!converted_frame_buffer) { converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(linear_size)), av_free}; } - - const int converted_stride{frame->width * 4}; + const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0}; u8* const converted_frame_buf_addr{converted_frame_buffer.get()}; sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, - &converted_frame_buf_addr, &converted_stride); + &converted_frame_buf_addr, converted_stride.data()); const u32 blk_kind = static_cast<u32>(config.block_linear_kind); if (blk_kind != 0) { // swizzle pitch linear to block linear const u32 block_height = static_cast<u32>(config.block_linear_height_log2); - const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1, - block_height, 0); + const auto size = + Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0); luma_buffer.resize(size); - Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4, - frame->width, 4, luma_buffer.data(), + Tegra::Texture::SwizzleSubrect(width, height, width * 4, width, 4, luma_buffer.data(), converted_frame_buffer.get(), block_height, 0, 0); gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size); @@ -131,41 +127,65 @@ void Vic::Execute() { const std::size_t surface_height = config.surface_height_minus1 + 1; const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width)); const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height)); - const std::size_t half_width = frame_width / 2; - const std::size_t half_height = frame_height / 2; - const std::size_t aligned_width = (surface_width + 0xff) & ~0xff; + const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL; - const auto* luma_ptr = frame->data[0]; - const auto* chroma_b_ptr = frame->data[1]; - const auto* chroma_r_ptr = frame->data[2]; const auto stride = static_cast<size_t>(frame->linesize[0]); - const auto half_stride = static_cast<size_t>(frame->linesize[1]); luma_buffer.resize(aligned_width * surface_height); chroma_buffer.resize(aligned_width * surface_height / 2); // Populate luma buffer + const u8* luma_src = frame->data[0]; for (std::size_t y = 0; y < frame_height; ++y) { const std::size_t src = y * stride; const std::size_t dst = y * aligned_width; for (std::size_t x = 0; x < frame_width; ++x) { - luma_buffer[dst + x] = luma_ptr[src + x]; + luma_buffer[dst + x] = luma_src[src + x]; } } gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), luma_buffer.size()); - // Populate chroma buffer from both channels with interleaving. - for (std::size_t y = 0; y < half_height; ++y) { - const std::size_t src = y * half_stride; - const std::size_t dst = y * aligned_width; + // Chroma + const std::size_t half_height = frame_height / 2; + const auto half_stride = static_cast<size_t>(frame->linesize[1]); - for (std::size_t x = 0; x < half_width; ++x) { - chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x]; - chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x]; + switch (frame->format) { + case AV_PIX_FMT_YUV420P: { + // Frame from FFmpeg software + // Populate chroma buffer from both channels with interleaving. + const std::size_t half_width = frame_width / 2; + const u8* chroma_b_src = frame->data[1]; + const u8* chroma_r_src = frame->data[2]; + for (std::size_t y = 0; y < half_height; ++y) { + const std::size_t src = y * half_stride; + const std::size_t dst = y * aligned_width; + + for (std::size_t x = 0; x < half_width; ++x) { + chroma_buffer[dst + x * 2] = chroma_b_src[src + x]; + chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x]; + } } + break; + } + case AV_PIX_FMT_NV12: { + // Frame from VA-API hardware + // This is already interleaved so just copy + const u8* chroma_src = frame->data[1]; + for (std::size_t y = 0; y < half_height; ++y) { + const std::size_t src = y * stride; + const std::size_t dst = y * aligned_width; + for (std::size_t x = 0; x < frame_width; ++x) { + chroma_buffer[dst + x] = chroma_src[src + x]; + } + } + break; + } + default: + UNREACHABLE(); + break; } - gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(), + gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(), chroma_buffer.size()); break; } diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h index f5a2ed100..74246e08c 100644 --- a/src/video_core/command_classes/vic.h +++ b/src/video_core/command_classes/vic.h @@ -22,8 +22,8 @@ public: SetControlParams = 0x1c1, SetConfigStructOffset = 0x1c2, SetOutputSurfaceLumaOffset = 0x1c8, - SetOutputSurfaceChromaUOffset = 0x1c9, - SetOutputSurfaceChromaVOffset = 0x1ca + SetOutputSurfaceChromaOffset = 0x1c9, + SetOutputSurfaceChromaUnusedOffset = 0x1ca }; explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor); @@ -64,8 +64,7 @@ private: GPUVAddr config_struct_address{}; GPUVAddr output_surface_luma_address{}; - GPUVAddr output_surface_chroma_u_address{}; - GPUVAddr output_surface_chroma_v_address{}; + GPUVAddr output_surface_chroma_address{}; SwsContext* scaler_ctx{}; s32 scaler_width{}; diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index c37f15bfd..f34c5f5d9 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -10,33 +10,27 @@ #define END_PUSH_CONSTANTS }; #define UNIFORM(n) #define BINDING_INPUT_BUFFER 0 -#define BINDING_ENC_BUFFER 1 -#define BINDING_SWIZZLE_BUFFER 2 -#define BINDING_OUTPUT_IMAGE 3 +#define BINDING_OUTPUT_IMAGE 1 #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv #define BEGIN_PUSH_CONSTANTS #define END_PUSH_CONSTANTS #define UNIFORM(n) layout(location = n) uniform -#define BINDING_SWIZZLE_BUFFER 0 -#define BINDING_INPUT_BUFFER 1 -#define BINDING_ENC_BUFFER 2 +#define BINDING_INPUT_BUFFER 0 #define BINDING_OUTPUT_IMAGE 0 #endif -layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; BEGIN_PUSH_CONSTANTS UNIFORM(1) uvec2 block_dims; - -UNIFORM(2) uint bytes_per_block_log2; -UNIFORM(3) uint layer_stride; -UNIFORM(4) uint block_size; -UNIFORM(5) uint x_shift; -UNIFORM(6) uint block_height; -UNIFORM(7) uint block_height_mask; +UNIFORM(2) uint layer_stride; +UNIFORM(3) uint block_size; +UNIFORM(4) uint x_shift; +UNIFORM(5) uint block_height; +UNIFORM(6) uint block_height_mask; END_PUSH_CONSTANTS struct EncodingData { @@ -55,45 +49,35 @@ struct TexelWeightParams { bool void_extent_hdr; }; -// Swizzle data -layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { - uint swizzle_table[]; -}; - layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 { - uint astc_data[]; -}; - -// ASTC Encodings data -layout(binding = BINDING_ENC_BUFFER, std430) readonly buffer EncodingsValues { - EncodingData encoding_values[]; + uvec4 astc_data[]; }; layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; -const uint GOB_SIZE_X = 64; -const uint GOB_SIZE_Y = 8; -const uint GOB_SIZE_Z = 1; -const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z; - const uint GOB_SIZE_X_SHIFT = 6; const uint GOB_SIZE_Y_SHIFT = 3; -const uint GOB_SIZE_Z_SHIFT = 0; -const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; - -const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); +const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT; -const int BLOCK_SIZE_IN_BYTES = 16; - -const int BLOCK_INFO_ERROR = 0; -const int BLOCK_INFO_VOID_EXTENT_HDR = 1; -const int BLOCK_INFO_VOID_EXTENT_LDR = 2; -const int BLOCK_INFO_NORMAL = 3; +const uint BYTES_PER_BLOCK_LOG2 = 4; const int JUST_BITS = 0; const int QUINT = 1; const int TRIT = 2; +// ASTC Encodings data, sorted in ascending order based on their BitLength value +// (see GetBitLength() function) +EncodingData encoding_values[22] = EncodingData[]( + EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0), + EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0), + EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0), + EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0), + EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0), + EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0), + EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0), + EncodingData(JUST_BITS, 8, 0, 0) +); + // The following constants are expanded variants of the Replicate() // function calls corresponding to the following arguments: // value: index into the generated table @@ -135,44 +119,37 @@ const uint REPLICATE_7_BIT_TO_8_TABLE[128] = // Input ASTC texture globals uint current_index = 0; int bitsread = 0; -uint total_bitsread = 0; -uint local_buff[16]; +int total_bitsread = 0; +uvec4 local_buff; // Color data globals -uint color_endpoint_data[16]; +uvec4 color_endpoint_data; int color_bitsread = 0; -uint total_color_bitsread = 0; -int color_index = 0; // Four values, two endpoints, four maximum paritions uint color_values[32]; int colvals_index = 0; // Weight data globals -uint texel_weight_data[16]; +uvec4 texel_weight_data; int texel_bitsread = 0; -uint total_texel_bitsread = 0; -int texel_index = 0; bool texel_flag = false; // Global "vectors" to be pushed into when decoding -EncodingData result_vector[100]; +EncodingData result_vector[144]; int result_index = 0; -EncodingData texel_vector[100]; +EncodingData texel_vector[144]; int texel_vector_index = 0; uint unquantized_texel_weights[2][144]; uint SwizzleOffset(uvec2 pos) { - pos = pos & SWIZZLE_MASK; - return swizzle_table[pos.y * 64 + pos.x]; -} - -uint ReadTexel(uint offset) { - // extract the 8-bit value from the 32-bit packed data. - return bitfieldExtract(astc_data[offset / 4], int((offset * 8) & 24), 8); + uint x = pos.x; + uint y = pos.y; + return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); } // Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] @@ -278,14 +255,10 @@ uint Hash52(uint p) { return p; } -uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bool small_block) { - if (partition_count == 1) { - return 0; - } +uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { if (small_block) { x <<= 1; y <<= 1; - z <<= 1; } seed += (partition_count - 1) * 1024; @@ -299,10 +272,6 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo uint seed6 = uint((rnum >> 20) & 0xF); uint seed7 = uint((rnum >> 24) & 0xF); uint seed8 = uint((rnum >> 28) & 0xF); - uint seed9 = uint((rnum >> 18) & 0xF); - uint seed10 = uint((rnum >> 22) & 0xF); - uint seed11 = uint((rnum >> 26) & 0xF); - uint seed12 = uint(((rnum >> 30) | (rnum << 2)) & 0xF); seed1 = (seed1 * seed1); seed2 = (seed2 * seed2); @@ -312,12 +281,8 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo seed6 = (seed6 * seed6); seed7 = (seed7 * seed7); seed8 = (seed8 * seed8); - seed9 = (seed9 * seed9); - seed10 = (seed10 * seed10); - seed11 = (seed11 * seed11); - seed12 = (seed12 * seed12); - int sh1, sh2, sh3; + uint sh1, sh2; if ((seed & 1) > 0) { sh1 = (seed & 2) > 0 ? 4 : 5; sh2 = (partition_count == 3) ? 6 : 5; @@ -325,25 +290,19 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo sh1 = (partition_count == 3) ? 6 : 5; sh2 = (seed & 2) > 0 ? 4 : 5; } - sh3 = (seed & 0x10) > 0 ? sh1 : sh2; - - seed1 = (seed1 >> sh1); - seed2 = (seed2 >> sh2); - seed3 = (seed3 >> sh1); - seed4 = (seed4 >> sh2); - seed5 = (seed5 >> sh1); - seed6 = (seed6 >> sh2); - seed7 = (seed7 >> sh1); - seed8 = (seed8 >> sh2); - seed9 = (seed9 >> sh3); - seed10 = (seed10 >> sh3); - seed11 = (seed11 >> sh3); - seed12 = (seed12 >> sh3); - - uint a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14); - uint b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10); - uint c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6); - uint d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2); + seed1 >>= sh1; + seed2 >>= sh2; + seed3 >>= sh1; + seed4 >>= sh2; + seed5 >>= sh1; + seed6 >>= sh2; + seed7 >>= sh1; + seed8 >>= sh2; + + uint a = seed1 * x + seed2 * y + (rnum >> 14); + uint b = seed3 * x + seed4 * y + (rnum >> 10); + uint c = seed5 * x + seed6 * y + (rnum >> 6); + uint d = seed7 * x + seed8 * y + (rnum >> 2); a &= 0x3F; b &= 0x3F; @@ -368,58 +327,37 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo } } -uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { - return SelectPartition(seed, x, y, 0, partition_count, small_block); -} - -uint ReadBit() { - if (current_index >= local_buff.length()) { +uint ExtractBits(uvec4 payload, int offset, int bits) { + if (bits <= 0) { return 0; } - uint bit = bitfieldExtract(local_buff[current_index], bitsread, 1); - ++bitsread; - ++total_bitsread; - if (bitsread == 8) { - ++current_index; - bitsread = 0; + int last_offset = offset + bits - 1; + int shifted_offset = offset >> 5; + if ((last_offset >> 5) == shifted_offset) { + return bitfieldExtract(payload[shifted_offset], offset & 31, bits); } - return bit; + int first_bits = 32 - (offset & 31); + int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); + int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); + return result_first | (result_second << first_bits); } uint StreamBits(uint num_bits) { - uint ret = 0; - for (uint i = 0; i < num_bits; i++) { - ret |= ((ReadBit() & 1) << i); - } + int int_bits = int(num_bits); + uint ret = ExtractBits(local_buff, total_bitsread, int_bits); + total_bitsread += int_bits; return ret; } -uint ReadColorBit() { - uint bit = 0; - if (texel_flag) { - bit = bitfieldExtract(texel_weight_data[texel_index], texel_bitsread, 1); - ++texel_bitsread; - ++total_texel_bitsread; - if (texel_bitsread == 8) { - ++texel_index; - texel_bitsread = 0; - } - } else { - bit = bitfieldExtract(color_endpoint_data[color_index], color_bitsread, 1); - ++color_bitsread; - ++total_color_bitsread; - if (color_bitsread == 8) { - ++color_index; - color_bitsread = 0; - } - } - return bit; -} - uint StreamColorBits(uint num_bits) { uint ret = 0; - for (uint i = 0; i < num_bits; i++) { - ret |= ((ReadColorBit() & 1) << i); + int int_bits = int(num_bits); + if (texel_flag) { + ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits); + texel_bitsread += int_bits; + } else { + ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); + color_bitsread += int_bits; } return ret; } @@ -596,22 +534,16 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { for (uint i = 0; i < num_partitions; i++) { num_values += ((modes[i] >> 2) + 1) << 1; } - int range = 256; - while (--range > 0) { - EncodingData val = encoding_values[range]; + // Find the largest encoding that's within color_data_bits + // TODO(ameerj): profile with binary search + int range = 0; + while (++range < encoding_values.length()) { uint bit_length = GetBitLength(num_values, range); - if (bit_length <= color_data_bits) { - while (--range > 0) { - EncodingData newval = encoding_values[range]; - if (newval.encoding != val.encoding && newval.num_bits != val.num_bits) { - break; - } - } - ++range; + if (bit_length > color_data_bits) { break; } } - DecodeIntegerSequence(range, num_values); + DecodeIntegerSequence(range - 1, num_values); uint out_index = 0; for (int itr = 0; itr < result_index; ++itr) { if (out_index >= num_values) { @@ -1028,7 +960,7 @@ int FindLayout(uint mode) { return 5; } -TexelWeightParams DecodeBlockInfo(uint block_index) { +TexelWeightParams DecodeBlockInfo() { TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false); uint mode = StreamBits(11); if ((mode & 0x1ff) == 0x1fc) { @@ -1110,10 +1042,10 @@ TexelWeightParams DecodeBlockInfo(uint block_index) { } weight_index -= 2; if ((mode_layout != 9) && ((mode & 0x200) != 0)) { - const int max_weights[6] = int[6](9, 11, 15, 19, 23, 31); + const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12); params.max_weight = max_weights[weight_index]; } else { - const int max_weights[6] = int[6](1, 2, 3, 4, 5, 7); + const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6); params.max_weight = max_weights[weight_index]; } return params; @@ -1144,8 +1076,8 @@ void FillVoidExtentLDR(ivec3 coord) { } } -void DecompressBlock(ivec3 coord, uint block_index) { - TexelWeightParams params = DecodeBlockInfo(block_index); +void DecompressBlock(ivec3 coord) { + TexelWeightParams params = DecodeBlockInfo(); if (params.error_state) { FillError(coord); return; @@ -1212,7 +1144,7 @@ void DecompressBlock(ivec3 coord, uint block_index) { // Read color data... uint color_data_bits = remaining_bits; while (remaining_bits > 0) { - int nb = int(min(remaining_bits, 8U)); + int nb = int(min(remaining_bits, 32U)); uint b = StreamBits(nb); color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); ++ced_pointer; @@ -1254,25 +1186,20 @@ void DecompressBlock(ivec3 coord, uint block_index) { ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]); } - for (uint i = 0; i < 16; i++) { - texel_weight_data[i] = local_buff[i]; - } - for (uint i = 0; i < 8; i++) { -#define REVERSE_BYTE(b) ((b * 0x0802U & 0x22110U) | (b * 0x8020U & 0x88440U)) * 0x10101U >> 16 - uint a = REVERSE_BYTE(texel_weight_data[i]); - uint b = REVERSE_BYTE(texel_weight_data[15 - i]); -#undef REVERSE_BYTE - texel_weight_data[i] = uint(bitfieldExtract(b, 0, 8)); - texel_weight_data[15 - i] = uint(bitfieldExtract(a, 0, 8)); - } + texel_weight_data = local_buff; + texel_weight_data = bitfieldReverse(texel_weight_data).wzyx; uint clear_byte_start = (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1; - texel_weight_data[clear_byte_start - 1] = - texel_weight_data[clear_byte_start - 1] & + + uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) & uint( ((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1)); - for (uint i = 0; i < 16 - clear_byte_start; i++) { - texel_weight_data[clear_byte_start + i] = 0U; + uint vec_index = (clear_byte_start - 1) >> 2; + texel_weight_data[vec_index] = + bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8); + for (uint i = clear_byte_start; i < 16; ++i) { + uint idx = i >> 2; + texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8); } texel_flag = true; // use texel "vector" and bit stream in integer decoding DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane)); @@ -1281,8 +1208,11 @@ void DecompressBlock(ivec3 coord, uint block_index) { for (uint j = 0; j < block_dims.y; j++) { for (uint i = 0; i < block_dims.x; i++) { - uint local_partition = Select2DPartition(partition_index, i, j, num_partitions, + uint local_partition = 0; + if (num_partitions > 1) { + local_partition = Select2DPartition(partition_index, i, j, num_partitions, (block_dims.y * block_dims.x) < 32); + } vec4 p; uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]); uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]); @@ -1303,7 +1233,7 @@ void DecompressBlock(ivec3 coord, uint block_index) { void main() { uvec3 pos = gl_GlobalInvocationID; - pos.x <<= bytes_per_block_log2; + pos.x <<= BYTES_PER_BLOCK_LOG2; // Read as soon as possible due to its latency const uint swizzle = SwizzleOffset(pos.xy); @@ -1321,13 +1251,8 @@ void main() { if (any(greaterThanEqual(coord, imageSize(dest_image)))) { return; } - uint block_index = - pos.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + pos.y * gl_WorkGroupSize.x + pos.x; - current_index = 0; bitsread = 0; - for (int i = 0; i < 16; i++) { - local_buff[i] = ReadTexel(offset + i); - } - DecompressBlock(coord, block_index); + local_buff = astc_data[offset / 16]; + DecompressBlock(coord); } diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h index 7f50ac2f8..d03d480b4 100644 --- a/src/video_core/macro/macro_jit_x64.h +++ b/src/video_core/macro/macro_jit_x64.h @@ -6,7 +6,7 @@ #include <array> #include <bitset> -#include <xbyak.h> +#include <xbyak/xbyak.h> #include "common/bit_field.h" #include "common/common_types.h" #include "common/x64/xbyak_abi.h" diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 3ea72fda9..a99c33c37 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -27,7 +27,7 @@ void RendererBase::UpdateCurrentFramebufferLayout() { render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height); } -void RendererBase::RequestScreenshot(void* data, std::function<void()> callback, +void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback, const Layout::FramebufferLayout& layout) { if (renderer_settings.screenshot_requested) { LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request"); diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 22b80c328..bb204454e 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -24,7 +24,7 @@ struct RendererSettings { // Screenshot std::atomic<bool> screenshot_requested{false}; void* screenshot_bits{}; - std::function<void()> screenshot_complete_callback; + std::function<void(bool)> screenshot_complete_callback; Layout::FramebufferLayout screenshot_framebuffer_layout; }; @@ -80,7 +80,7 @@ public: void RefreshBaseSettings(); /// Request a screenshot of the next frame - void RequestScreenshot(void* data, std::function<void()> callback, + void RequestScreenshot(void* data, std::function<void(bool)> callback, const Layout::FramebufferLayout& layout); protected: diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index fac0034fb..bccb37a58 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -15,7 +15,7 @@ #include "video_core/renderer_opengl/gl_shader_util.h" #include "video_core/renderer_opengl/gl_state_tracker.h" #include "video_core/shader_notify.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" #if defined(_MSC_VER) && defined(NDEBUG) #define LAMBDA_FORCEINLINE [[msvc::forceinline]] diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 41d2b73f4..b909c387e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -32,7 +32,7 @@ #include "video_core/renderer_opengl/maxwell_to_gl.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/shader_cache.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" namespace OpenGL { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index c373c9cb4..b0aee6cc1 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -18,10 +18,8 @@ #include "video_core/renderer_opengl/maxwell_to_gl.h" #include "video_core/renderer_opengl/util_shaders.h" #include "video_core/surface.h" -#include "video_core/texture_cache/format_lookup_table.h" +#include "video_core/texture_cache/formatter.h" #include "video_core/texture_cache/samples_helper.h" -#include "video_core/texture_cache/texture_cache.h" -#include "video_core/textures/decoders.h" namespace OpenGL { namespace { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 921072ebe..4a4f6301c 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -12,7 +12,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/util_shaders.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" namespace OpenGL { diff --git a/src/video_core/renderer_opengl/gl_texture_cache_base.cpp b/src/video_core/renderer_opengl/gl_texture_cache_base.cpp new file mode 100644 index 000000000..385358fea --- /dev/null +++ b/src/video_core/renderer_opengl/gl_texture_cache_base.cpp @@ -0,0 +1,10 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/renderer_opengl/gl_texture_cache.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace VideoCommon { +template class VideoCommon::TextureCache<OpenGL::TextureCacheParams>; +} diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index f1b00c24c..7d7cba69c 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -486,7 +486,7 @@ void RendererOpenGL::RenderScreenshot() { glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); - renderer_settings.screenshot_complete_callback(); + renderer_settings.screenshot_complete_callback(true); renderer_settings.screenshot_requested = false; } diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 37a4d1d9d..333f35a1c 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -60,19 +60,14 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_) copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) { const auto swizzle_table = Tegra::Texture::MakeSwizzleTable(); swizzle_table_buffer.Create(); - astc_buffer.Create(); glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0); - glNamedBufferStorage(astc_buffer.handle, sizeof(ASTC_ENCODINGS_VALUES), &ASTC_ENCODINGS_VALUES, - 0); } UtilShaders::~UtilShaders() = default; void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map, std::span<const VideoCommon::SwizzleParameters> swizzles) { - static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; - static constexpr GLuint BINDING_INPUT_BUFFER = 1; - static constexpr GLuint BINDING_ENC_BUFFER = 2; + static constexpr GLuint BINDING_INPUT_BUFFER = 0; static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; const Extent2D tile_size{ @@ -80,34 +75,32 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map, .height = VideoCore::Surface::DefaultBlockHeight(image.info.format), }; program_manager.BindComputeProgram(astc_decoder_program.handle); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_ENC_BUFFER, astc_buffer.handle); - glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); glUniform2ui(1, tile_size.width, tile_size.height); + // Ensure buffer data is valid before dispatching glFlush(); for (const SwizzleParameters& swizzle : swizzles) { const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); ASSERT(params.origin == (std::array<u32, 3>{0, 0, 0})); ASSERT(params.destination == (std::array<s32, 3>{0, 0, 0})); + ASSERT(params.bytes_per_block_log2 == 4); - glUniform1ui(2, params.bytes_per_block_log2); - glUniform1ui(3, params.layer_stride); - glUniform1ui(4, params.block_size); - glUniform1ui(5, params.x_shift); - glUniform1ui(6, params.block_height); - glUniform1ui(7, params.block_height_mask); + glUniform1ui(2, params.layer_stride); + glUniform1ui(3, params.block_size); + glUniform1ui(4, params.x_shift); + glUniform1ui(5, params.block_height); + glUniform1ui(6, params.block_height_mask); - glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, - GL_WRITE_ONLY, GL_RGBA8); // ASTC texture data glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, image.guest_size_bytes - swizzle.buffer_offset); + glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, + GL_WRITE_ONLY, GL_RGBA8); glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers); } diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h index 53d65f368..ef881e35f 100644 --- a/src/video_core/renderer_opengl/util_shaders.h +++ b/src/video_core/renderer_opengl/util_shaders.h @@ -62,7 +62,6 @@ private: ProgramManager& program_manager; OGLBuffer swizzle_table_buffer; - OGLBuffer astc_buffer; OGLProgram astc_decoder_program; OGLProgram block_linear_unswizzle_2d_program; diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.cpp b/src/video_core/renderer_vulkan/pipeline_statistics.cpp new file mode 100644 index 000000000..bfec931a6 --- /dev/null +++ b/src/video_core/renderer_vulkan/pipeline_statistics.cpp @@ -0,0 +1,100 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <string_view> + +#include <fmt/format.h> + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/renderer_vulkan/pipeline_statistics.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +using namespace std::string_view_literals; + +static u64 GetUint64(const VkPipelineExecutableStatisticKHR& statistic) { + switch (statistic.format) { + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: + return static_cast<u64>(statistic.value.i64); + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: + return statistic.value.u64; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: + return static_cast<u64>(statistic.value.f64); + default: + return 0; + } +} + +PipelineStatistics::PipelineStatistics(const Device& device_) : device{device_} {} + +void PipelineStatistics::Collect(VkPipeline pipeline) { + const auto& dev{device.GetLogical()}; + const std::vector properties{dev.GetPipelineExecutablePropertiesKHR(pipeline)}; + const u32 num_executables{static_cast<u32>(properties.size())}; + for (u32 executable = 0; executable < num_executables; ++executable) { + const auto statistics{dev.GetPipelineExecutableStatisticsKHR(pipeline, executable)}; + if (statistics.empty()) { + continue; + } + Stats stage_stats; + for (const auto& statistic : statistics) { + const char* const name{statistic.name}; + if (name == "Binary Size"sv || name == "Code size"sv || name == "Instruction Count"sv) { + stage_stats.code_size = GetUint64(statistic); + } else if (name == "Register Count"sv) { + stage_stats.register_count = GetUint64(statistic); + } else if (name == "SGPRs"sv || name == "numUsedSgprs"sv) { + stage_stats.sgpr_count = GetUint64(statistic); + } else if (name == "VGPRs"sv || name == "numUsedVgprs"sv) { + stage_stats.vgpr_count = GetUint64(statistic); + } else if (name == "Branches"sv) { + stage_stats.branches_count = GetUint64(statistic); + } else if (name == "Basic Block Count"sv) { + stage_stats.basic_block_count = GetUint64(statistic); + } + } + std::lock_guard lock{mutex}; + collected_stats.push_back(stage_stats); + } +} + +void PipelineStatistics::Report() const { + double num{}; + Stats total; + { + std::lock_guard lock{mutex}; + for (const Stats& stats : collected_stats) { + total.code_size += stats.code_size; + total.register_count += stats.register_count; + total.sgpr_count += stats.sgpr_count; + total.vgpr_count += stats.vgpr_count; + total.branches_count += stats.branches_count; + total.basic_block_count += stats.basic_block_count; + } + num = static_cast<double>(collected_stats.size()); + } + std::string report; + const auto add = [&](const char* fmt, u64 value) { + if (value > 0) { + report += fmt::format(fmt::runtime(fmt), static_cast<double>(value) / num); + } + }; + add("Code size: {:9.03f}\n", total.code_size); + add("Register count: {:9.03f}\n", total.register_count); + add("SGPRs: {:9.03f}\n", total.sgpr_count); + add("VGPRs: {:9.03f}\n", total.vgpr_count); + add("Branches count: {:9.03f}\n", total.branches_count); + add("Basic blocks: {:9.03f}\n", total.basic_block_count); + + LOG_INFO(Render_Vulkan, + "\nAverage pipeline statistics\n" + "==========================================\n" + "{}\n", + report); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.h b/src/video_core/renderer_vulkan/pipeline_statistics.h new file mode 100644 index 000000000..b61840107 --- /dev/null +++ b/src/video_core/renderer_vulkan/pipeline_statistics.h @@ -0,0 +1,40 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <mutex> +#include <vector> + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; + +class PipelineStatistics { +public: + explicit PipelineStatistics(const Device& device_); + + void Collect(VkPipeline pipeline); + + void Report() const; + +private: + struct Stats { + u64 code_size{}; + u64 register_count{}; + u64 sgpr_count{}; + u64 vgpr_count{}; + u64 branches_count{}; + u64 basic_block_count{}; + }; + + const Device& device; + mutable std::mutex mutex; + std::vector<Stats> collected_stats; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index a8d04dc61..7c9b0d6db 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -138,6 +138,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { const bool use_accelerated = rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); const bool is_srgb = use_accelerated && screen_info.is_srgb; + RenderScreenshot(*framebuffer, use_accelerated); bool has_been_recreated = false; const auto recreate_swapchain = [&] { @@ -162,7 +163,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { if (has_been_recreated) { blit_screen.Recreate(); } - const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); + const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated); scheduler.Flush(render_semaphore); scheduler.WaitWorker(); swapchain.Present(render_semaphore); @@ -193,4 +194,153 @@ void RendererVulkan::Report() const { telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); } +void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated) { + if (!renderer_settings.screenshot_requested) { + return; + } + const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; + vk::Image staging_image = device.GetLogical().CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_B8G8R8A8_UNORM, + .extent = + { + .width = layout.width, + .height = layout.height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + const auto image_commit = memory_allocator.Commit(staging_image, MemoryUsage::DeviceLocal); + + const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *staging_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = screen_info.is_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); + const VkExtent2D render_area{.width = layout.width, .height = layout.height}; + const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area); + // Since we're not rendering to the screen, ignore the render semaphore. + void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated)); + + const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4); + const VkBufferCreateInfo dst_buffer_info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = buffer_size, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + const vk::Buffer dst_buffer = device.GetLogical().CreateBuffer(dst_buffer_info); + MemoryCommit dst_buffer_memory = memory_allocator.Commit(dst_buffer, MemoryUsage::Download); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier read_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *staging_image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + const VkImageMemoryBarrier image_write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *staging_image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + static constexpr VkMemoryBarrier memory_write_barrier{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }; + const VkBufferImageCopy copy{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset{.x = 0, .y = 0, .z = 0}, + .imageExtent{ + .width = layout.width, + .height = layout.height, + .depth = 1, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, read_barrier); + cmdbuf.CopyImageToBuffer(*staging_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, *dst_buffer, + copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, memory_write_barrier, nullptr, image_write_barrier); + }); + // Ensure the copy is fully completed before saving the screenshot + scheduler.Finish(); + + // Copy backing image data to the QImage screenshot buffer + const auto dst_memory_map = dst_buffer_memory.Map(); + std::memcpy(renderer_settings.screenshot_bits, dst_memory_map.data(), dst_memory_map.size()); + renderer_settings.screenshot_complete_callback(false); + renderer_settings.screenshot_requested = false; +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index d7d17e110..6dc985109 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -54,6 +54,8 @@ public: private: void Report() const; + void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated); + Core::TelemetrySession& telemetry_session; Core::Memory::Memory& cpu_memory; Tegra::GPU& gpu; diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 516f428e7..5c43b8acf 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -130,7 +130,10 @@ void VKBlitScreen::Recreate() { CreateDynamicResources(); } -VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) { +VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, + const VkFramebuffer& host_framebuffer, + const Layout::FramebufferLayout layout, VkExtent2D render_area, + bool use_accelerated) { RefreshResources(framebuffer); // Finish any pending renderpass @@ -145,8 +148,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated ? screen_info.image_view : *raw_image_views[image_index]); BufferData data; - SetUniformData(data, framebuffer); - SetVertexData(data, framebuffer); + SetUniformData(data, layout); + SetVertexData(data, framebuffer, layout); const std::span<u8> mapped_span = buffer_commit.Map(); std::memcpy(mapped_span.data(), &data, sizeof(data)); @@ -220,52 +223,75 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); }); } - scheduler.Record([this, image_index, size = swapchain.GetSize()](vk::CommandBuffer cmdbuf) { - const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; - const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; - const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; - const VkClearValue clear_color{ - .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, - }; - const VkRenderPassBeginInfo renderpass_bi{ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .pNext = nullptr, - .renderPass = *renderpass, - .framebuffer = *framebuffers[image_index], - .renderArea = - { - .offset = {0, 0}, - .extent = size, - }, - .clearValueCount = 1, - .pClearValues = &clear_color, - }; - const VkViewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast<float>(size.width), - .height = static_cast<float>(size.height), - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - const VkRect2D scissor{ - .offset = {0, 0}, - .extent = size, - }; - cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); - cmdbuf.SetViewport(0, viewport); - cmdbuf.SetScissor(0, scissor); - - cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, - descriptor_sets[image_index], {}); - cmdbuf.Draw(4, 1, 0, 0); - cmdbuf.EndRenderPass(); - }); + scheduler.Record( + [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) { + const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; + const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; + const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; + const VkClearValue clear_color{ + .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, + }; + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = *renderpass, + .framebuffer = host_framebuffer, + .renderArea = + { + .offset = {0, 0}, + .extent = size, + }, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast<float>(size.width), + .height = static_cast<float>(size.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const VkRect2D scissor{ + .offset = {0, 0}, + .extent = size, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); + + cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, + descriptor_sets[image_index], {}); + cmdbuf.Draw(4, 1, 0, 0); + cmdbuf.EndRenderPass(); + }); return *semaphores[image_index]; } +VkSemaphore VKBlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated) { + const std::size_t image_index = swapchain.GetImageIndex(); + const VkExtent2D render_area = swapchain.GetSize(); + const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); + return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated); +} + +vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) { + return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = *renderpass, + .attachmentCount = 1, + .pAttachments = &image_view, + .width = extent.width, + .height = extent.height, + .layers = 1, + }); +} + void VKBlitScreen::CreateStaticResources() { CreateShaders(); CreateSemaphores(); @@ -609,22 +635,9 @@ void VKBlitScreen::CreateFramebuffers() { const VkExtent2D size{swapchain.GetSize()}; framebuffers.resize(image_count); - VkFramebufferCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .renderPass = *renderpass, - .attachmentCount = 1, - .pAttachments = nullptr, - .width = size.width, - .height = size.height, - .layers = 1, - }; - for (std::size_t i = 0; i < image_count; ++i) { const VkImageView image_view{swapchain.GetImageViewIndex(i)}; - ci.pAttachments = &image_view; - framebuffers[i] = device.GetLogical().CreateFramebuffer(ci); + framebuffers[i] = CreateFramebuffer(image_view, size); } } @@ -752,15 +765,13 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {}); } -void VKBlitScreen::SetUniformData(BufferData& data, - const Tegra::FramebufferConfig& framebuffer) const { - const auto& layout = render_window.GetFramebufferLayout(); +void VKBlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const { data.uniform.modelview_matrix = MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height)); } -void VKBlitScreen::SetVertexData(BufferData& data, - const Tegra::FramebufferConfig& framebuffer) const { +void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout layout) const { const auto& framebuffer_transform_flags = framebuffer.transform_flags; const auto& framebuffer_crop_rect = framebuffer.crop_rect; @@ -798,7 +809,7 @@ void VKBlitScreen::SetVertexData(BufferData& data, static_cast<f32>(screen_info.height); } - const auto& screen = render_window.GetFramebufferLayout().screen; + const auto& screen = layout.screen; const auto x = static_cast<f32>(screen.left); const auto y = static_cast<f32>(screen.top); const auto w = static_cast<f32>(screen.GetWidth()); diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index 5e3177685..430bcfbca 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.h +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -56,8 +56,16 @@ public: void Recreate(); [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, + const VkFramebuffer& host_framebuffer, + const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated); + [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated); + + [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view, + VkExtent2D extent); + private: struct BufferData; @@ -81,8 +89,9 @@ private: void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view) const; - void SetUniformData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; - void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; + void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const; + void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout layout) const; u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 561cf5e11..3e96c0f60 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -30,16 +30,12 @@ namespace Vulkan { using Tegra::Texture::SWIZZLE_TABLE; -using Tegra::Texture::ASTC::ASTC_ENCODINGS_VALUES; -using namespace Tegra::Texture::ASTC; namespace { constexpr u32 ASTC_BINDING_INPUT_BUFFER = 0; -constexpr u32 ASTC_BINDING_ENC_BUFFER = 1; -constexpr u32 ASTC_BINDING_SWIZZLE_BUFFER = 2; -constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 3; -constexpr size_t ASTC_NUM_BINDINGS = 4; +constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 1; +constexpr size_t ASTC_NUM_BINDINGS = 2; template <size_t size> inline constexpr VkPushConstantRange COMPUTE_PUSH_CONSTANT_RANGE{ @@ -75,7 +71,7 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ .score = 2, }; -constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDINGS{{ +constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{ { .binding = ASTC_BINDING_INPUT_BUFFER, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, @@ -84,20 +80,6 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDIN .pImmutableSamplers = nullptr, }, { - .binding = ASTC_BINDING_ENC_BUFFER, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { - .binding = ASTC_BINDING_SWIZZLE_BUFFER, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { .binding = ASTC_BINDING_OUTPUT_IMAGE, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1, @@ -108,12 +90,12 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDIN constexpr DescriptorBankInfo ASTC_BANK_INFO{ .uniform_buffers = 0, - .storage_buffers = 3, + .storage_buffers = 1, .texture_buffers = 0, .image_buffers = 0, .textures = 0, .images = 1, - .score = 4, + .score = 2, }; constexpr VkDescriptorUpdateTemplateEntryKHR INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ @@ -136,22 +118,6 @@ constexpr std::array<VkDescriptorUpdateTemplateEntryKHR, ASTC_NUM_BINDINGS> .stride = sizeof(DescriptorUpdateEntry), }, { - .dstBinding = ASTC_BINDING_ENC_BUFFER, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = ASTC_BINDING_ENC_BUFFER * sizeof(DescriptorUpdateEntry), - .stride = sizeof(DescriptorUpdateEntry), - }, - { - .dstBinding = ASTC_BINDING_SWIZZLE_BUFFER, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = ASTC_BINDING_SWIZZLE_BUFFER * sizeof(DescriptorUpdateEntry), - .stride = sizeof(DescriptorUpdateEntry), - }, - { .dstBinding = ASTC_BINDING_OUTPUT_IMAGE, .dstArrayElement = 0, .descriptorCount = 1, @@ -163,7 +129,6 @@ constexpr std::array<VkDescriptorUpdateTemplateEntryKHR, ASTC_NUM_BINDINGS> struct AstcPushConstants { std::array<u32, 2> blocks_dims; - u32 bytes_per_block_log2; u32 layer_stride; u32 block_size; u32 x_shift; @@ -354,46 +319,6 @@ ASTCDecoderPass::ASTCDecoderPass(const Device& device_, VKScheduler& scheduler_, ASTCDecoderPass::~ASTCDecoderPass() = default; -void ASTCDecoderPass::MakeDataBuffer() { - constexpr size_t TOTAL_BUFFER_SIZE = sizeof(ASTC_ENCODINGS_VALUES) + sizeof(SWIZZLE_TABLE); - data_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = TOTAL_BUFFER_SIZE, - .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - }); - data_buffer_commit = memory_allocator.Commit(data_buffer, MemoryUsage::Upload); - - const auto staging_ref = staging_buffer_pool.Request(TOTAL_BUFFER_SIZE, MemoryUsage::Upload); - std::memcpy(staging_ref.mapped_span.data(), &ASTC_ENCODINGS_VALUES, - sizeof(ASTC_ENCODINGS_VALUES)); - // Tack on the swizzle table at the end of the buffer - std::memcpy(staging_ref.mapped_span.data() + sizeof(ASTC_ENCODINGS_VALUES), &SWIZZLE_TABLE, - sizeof(SWIZZLE_TABLE)); - - scheduler.Record([src = staging_ref.buffer, offset = staging_ref.offset, dst = *data_buffer, - TOTAL_BUFFER_SIZE](vk::CommandBuffer cmdbuf) { - static constexpr VkMemoryBarrier write_barrier{ - .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - }; - const VkBufferCopy copy{ - .srcOffset = offset, - .dstOffset = 0, - .size = TOTAL_BUFFER_SIZE, - }; - cmdbuf.CopyBuffer(src, dst, copy); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - 0, write_barrier); - }); -} - void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, std::span<const VideoCommon::SwizzleParameters> swizzles) { using namespace VideoCommon::Accelerated; @@ -402,9 +327,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, VideoCore::Surface::DefaultBlockHeight(image.info.format), }; scheduler.RequestOutsideRenderPassOperationContext(); - if (!data_buffer) { - MakeDataBuffer(); - } const VkPipeline vk_pipeline = *pipeline; const VkImageAspectFlags aspect_mask = image.AspectMask(); const VkImage vk_image = image.Handle(); @@ -436,16 +358,13 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, }); for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); const u32 num_dispatches_z = image.info.resources.layers; update_descriptor_queue.Acquire(); update_descriptor_queue.AddBuffer(map.buffer, input_offset, image.guest_size_bytes - swizzle.buffer_offset); - update_descriptor_queue.AddBuffer(*data_buffer, 0, sizeof(ASTC_ENCODINGS_VALUES)); - update_descriptor_queue.AddBuffer(*data_buffer, sizeof(ASTC_ENCODINGS_VALUES), - sizeof(SWIZZLE_TABLE)); update_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); const void* const descriptor_data{update_descriptor_queue.UpdateData()}; @@ -453,11 +372,11 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); ASSERT(params.origin == (std::array<u32, 3>{0, 0, 0})); ASSERT(params.destination == (std::array<s32, 3>{0, 0, 0})); + ASSERT(params.bytes_per_block_log2 == 4); scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, block_dims, params, descriptor_data](vk::CommandBuffer cmdbuf) { const AstcPushConstants uniforms{ .blocks_dims = block_dims, - .bytes_per_block_log2 = params.bytes_per_block_log2, .layer_stride = params.layer_stride, .block_size = params.block_size, .x_shift = params.x_shift, diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 114aef2bd..c7b92cce0 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -96,15 +96,10 @@ public: std::span<const VideoCommon::SwizzleParameters> swizzles); private: - void MakeDataBuffer(); - VKScheduler& scheduler; StagingBufferPool& staging_buffer_pool; VKUpdateDescriptorQueue& update_descriptor_queue; MemoryAllocator& memory_allocator; - - vk::Buffer data_buffer; - MemoryCommit data_buffer_commit; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 70b84c7a6..44faf626a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -8,6 +8,7 @@ #include <boost/container/small_vector.hpp> #include "video_core/renderer_vulkan/pipeline_helper.h" +#include "video_core/renderer_vulkan/pipeline_statistics.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" @@ -26,6 +27,7 @@ using Tegra::Texture::TexturePair; ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* thread_worker, + PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_, vk::ShaderModule spv_module_) : device{device_}, update_descriptor_queue{update_descriptor_queue_}, info{info_}, @@ -36,7 +38,7 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), uniform_buffer_sizes.begin()); - auto func{[this, &descriptor_pool, shader_notify] { + auto func{[this, &descriptor_pool, shader_notify, pipeline_statistics] { DescriptorLayoutBuilder builder{device}; builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); @@ -50,10 +52,14 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript .pNext = nullptr, .requiredSubgroupSize = GuestWarpSize, }; + VkPipelineCreateFlags flags{}; + if (device.IsKhrPipelineEexecutablePropertiesEnabled()) { + flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; + } pipeline = device.GetLogical().CreateComputePipeline({ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, - .flags = 0, + .flags = flags, .stage{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, @@ -67,6 +73,9 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript .basePipelineHandle = 0, .basePipelineIndex = 0, }); + if (pipeline_statistics) { + pipeline_statistics->Collect(*pipeline); + } std::lock_guard lock{build_mutex}; is_built = true; build_condvar.notify_one(); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 52fec04d3..8c4b0a301 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -25,6 +25,7 @@ class ShaderNotify; namespace Vulkan { class Device; +class PipelineStatistics; class VKScheduler; class ComputePipeline { @@ -32,6 +33,7 @@ public: explicit ComputePipeline(const Device& device, DescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue, Common::ThreadWorker* thread_worker, + PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info, vk::ShaderModule spv_module); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 18482e1d0..7c0f91007 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -11,6 +11,7 @@ #include "common/bit_field.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/pipeline_helper.h" +#include "video_core/renderer_vulkan/pipeline_statistics.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_render_pass_cache.h" @@ -217,8 +218,8 @@ GraphicsPipeline::GraphicsPipeline( VKScheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_, VideoCore::ShaderNotify* shader_notify, const Device& device_, DescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* worker_thread, - RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key_, - std::array<vk::ShaderModule, NUM_STAGES> stages, + PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, + const GraphicsPipelineCacheKey& key_, std::array<vk::ShaderModule, NUM_STAGES> stages, const std::array<const Shader::Info*, NUM_STAGES>& infos) : key{key_}, maxwell3d{maxwell3d_}, gpu_memory{gpu_memory_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, scheduler{scheduler_}, @@ -235,7 +236,7 @@ GraphicsPipeline::GraphicsPipeline( enabled_uniform_buffer_masks[stage] = info->constant_buffer_mask; std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin()); } - auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool] { + auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] { DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; uses_push_descriptor = builder.CanUsePushDescriptor(); descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); @@ -250,6 +251,9 @@ GraphicsPipeline::GraphicsPipeline( const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state))}; Validate(); MakePipeline(render_pass); + if (pipeline_statistics) { + pipeline_statistics->Collect(*pipeline); + } std::lock_guard lock{build_mutex}; is_built = true; @@ -782,10 +786,14 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } */ } + VkPipelineCreateFlags flags{}; + if (device.IsKhrPipelineEexecutablePropertiesEnabled()) { + flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; + } pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, - .flags = 0, + .flags = flags, .stageCount = static_cast<u32>(shader_stages.size()), .pStages = shader_stages.data(), .pVertexInputState = &vertex_input_ci, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 2bd48d697..1c780e944 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -60,6 +60,7 @@ struct hash<Vulkan::GraphicsPipelineCacheKey> { namespace Vulkan { class Device; +class PipelineStatistics; class RenderPassCache; class VKScheduler; class VKUpdateDescriptorQueue; @@ -73,8 +74,9 @@ public: VKScheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache, VideoCore::ShaderNotify* shader_notify, const Device& device, DescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue, - Common::ThreadWorker* worker_thread, RenderPassCache& render_pass_cache, - const GraphicsPipelineCacheKey& key, std::array<vk::ShaderModule, NUM_STAGES> stages, + Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, + RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, + std::array<vk::ShaderModule, NUM_STAGES> stages, const std::array<const Shader::Info*, NUM_STAGES>& infos); GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 57b163247..a37ca1fdf 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -29,6 +29,7 @@ #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/pipeline_helper.h" +#include "video_core/renderer_vulkan/pipeline_statistics.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -389,15 +390,19 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading size_t total{}; size_t built{}; bool has_loaded{}; + std::unique_ptr<PipelineStatistics> statistics; } state; + if (device.IsKhrPipelineEexecutablePropertiesEnabled()) { + state.statistics = std::make_unique<PipelineStatistics>(device); + } const auto load_compute{[&](std::ifstream& file, FileEnvironment env) { ComputePipelineCacheKey key; file.read(reinterpret_cast<char*>(&key), sizeof(key)); workers.QueueWork([this, key, env = std::move(env), &state, &callback]() mutable { ShaderPools pools; - auto pipeline{CreateComputePipeline(pools, key, env, false)}; + auto pipeline{CreateComputePipeline(pools, key, env, state.statistics.get(), false)}; std::lock_guard lock{state.mutex}; if (pipeline) { compute_cache.emplace(key, std::move(pipeline)); @@ -425,7 +430,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading for (auto& env : envs) { env_ptrs.push_back(&env); } - auto pipeline{CreateGraphicsPipeline(pools, key, MakeSpan(env_ptrs), false)}; + auto pipeline{CreateGraphicsPipeline(pools, key, MakeSpan(env_ptrs), + state.statistics.get(), false)}; std::lock_guard lock{state.mutex}; graphics_cache.emplace(key, std::move(pipeline)); @@ -445,6 +451,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading lock.unlock(); workers.WaitForRequests(); + + if (state.statistics) { + state.statistics->Report(); + } } GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() { @@ -486,7 +496,8 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( ShaderPools& pools, const GraphicsPipelineCacheKey& key, - std::span<Shader::Environment* const> envs, bool build_in_parallel) try { + std::span<Shader::Environment* const> envs, PipelineStatistics* statistics, + bool build_in_parallel) try { LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash()); size_t env_index{0}; std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs; @@ -540,7 +551,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique<GraphicsPipeline>( maxwell3d, gpu_memory, scheduler, buffer_cache, texture_cache, &shader_notify, device, - descriptor_pool, update_descriptor_queue, thread_worker, render_pass_cache, key, + descriptor_pool, update_descriptor_queue, thread_worker, statistics, render_pass_cache, key, std::move(modules), infos); } catch (const Shader::Exception& exception) { @@ -553,7 +564,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() { GetGraphicsEnvironments(environments, graphics_key.unique_hashes); main_pools.ReleaseContents(); - auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), true)}; + auto pipeline{ + CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), nullptr, true)}; if (!pipeline || pipeline_cache_filename.empty()) { return pipeline; } @@ -578,7 +590,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( env.SetCachedSize(shader->size_bytes); main_pools.ReleaseContents(); - auto pipeline{CreateComputePipeline(main_pools, key, env, true)}; + auto pipeline{CreateComputePipeline(main_pools, key, env, nullptr, true)}; if (!pipeline || pipeline_cache_filename.empty()) { return pipeline; } @@ -591,7 +603,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, - bool build_in_parallel) try { + PipelineStatistics* statistics, bool build_in_parallel) try { LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash()); Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; @@ -605,8 +617,8 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( } Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique<ComputePipeline>(device, descriptor_pool, update_descriptor_queue, - thread_worker, &shader_notify, program.info, - std::move(spv_module)); + thread_worker, statistics, &shader_notify, + program.info, std::move(spv_module)); } catch (const Shader::Exception& exception) { LOG_ERROR(Render_Vulkan, "{}", exception.what()); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index efe5a7ed8..4c135b5dd 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -80,8 +80,9 @@ struct hash<Vulkan::ComputePipelineCacheKey> { namespace Vulkan { class ComputePipeline; -class Device; class DescriptorPool; +class Device; +class PipelineStatistics; class RasterizerVulkan; class RenderPassCache; class VKScheduler; @@ -128,7 +129,8 @@ private: std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline( ShaderPools& pools, const GraphicsPipelineCacheKey& key, - std::span<Shader::Environment* const> envs, bool build_in_parallel); + std::span<Shader::Environment* const> envs, PipelineStatistics* statistics, + bool build_in_parallel); std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineCacheKey& key, const ShaderInfo* shader); @@ -136,6 +138,7 @@ private: std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, + PipelineStatistics* statistics, bool build_in_parallel); const Device& device; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c7a07fdd8..3ac18ea54 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -32,7 +32,7 @@ #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/shader_cache.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -61,11 +61,16 @@ struct DrawParams { VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) { const auto& src = regs.viewport_transform[index]; const float width = src.scale_x * 2.0f; - const float height = src.scale_y * 2.0f; + float y = src.translate_y - src.scale_y; + float height = src.scale_y * 2.0f; + if (regs.screen_y_control.y_negate) { + y += height; + height = -height; + } const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; VkViewport viewport{ .x = src.translate_x - src.scale_x, - .y = src.translate_y - src.scale_y, + .y = y, .width = width != 0.0f ? width : 1.0f, .height = height != 0.0f ? height : 1.0f, .minDepth = src.translate_z - src.scale_z * reduce_z, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 8e029bcb3..8f4df7122 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -19,6 +19,8 @@ #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/samples_helper.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 0b73d55f8..5fe6b7ba3 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -9,7 +9,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" diff --git a/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp b/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp new file mode 100644 index 000000000..44e688342 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp @@ -0,0 +1,10 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace VideoCommon { +template class VideoCommon::TextureCache<Vulkan::TextureCacheParams>; +} diff --git a/src/video_core/texture_cache/image_view_info.cpp b/src/video_core/texture_cache/image_view_info.cpp index faf5b151f..6527e14c8 100644 --- a/src/video_core/texture_cache/image_view_info.cpp +++ b/src/video_core/texture_cache/image_view_info.cpp @@ -6,7 +6,7 @@ #include "common/assert.h" #include "video_core/texture_cache/image_view_info.h" -#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/texture_cache_base.h" #include "video_core/texture_cache/types.h" #include "video_core/textures/texture.h" @@ -14,6 +14,8 @@ namespace VideoCommon { namespace { +using Tegra::Texture::TextureType; + constexpr u8 RENDER_TARGET_SWIZZLE = std::numeric_limits<u8>::max(); [[nodiscard]] u8 CastSwizzle(SwizzleSource source) { diff --git a/src/video_core/texture_cache/render_targets.h b/src/video_core/texture_cache/render_targets.h index 9b9544b07..0cb227d69 100644 --- a/src/video_core/texture_cache/render_targets.h +++ b/src/video_core/texture_cache/render_targets.h @@ -24,10 +24,10 @@ struct RenderTargets { return std::ranges::any_of(color_buffer_ids, contains) || contains(depth_buffer_id); } - std::array<ImageViewId, NUM_RT> color_buffer_ids; - ImageViewId depth_buffer_id; + std::array<ImageViewId, NUM_RT> color_buffer_ids{}; + ImageViewId depth_buffer_id{}; std::array<u8, NUM_RT> draw_buffers{}; - Extent2D size; + Extent2D size{}; }; } // namespace VideoCommon diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index f34c9d9ca..a087498ff 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -4,48 +4,11 @@ #pragma once -#include <algorithm> -#include <array> -#include <bit> -#include <memory> -#include <mutex> -#include <optional> -#include <span> -#include <type_traits> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include <boost/container/small_vector.hpp> - #include "common/alignment.h" -#include "common/common_types.h" -#include "common/literals.h" -#include "common/logging/log.h" #include "common/settings.h" -#include "video_core/compatible_formats.h" -#include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/kepler_compute.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/memory_manager.h" -#include "video_core/rasterizer_interface.h" -#include "video_core/surface.h" -#include "video_core/texture_cache/descriptor_table.h" -#include "video_core/texture_cache/format_lookup_table.h" -#include "video_core/texture_cache/formatter.h" -#include "video_core/texture_cache/image_base.h" -#include "video_core/texture_cache/image_info.h" -#include "video_core/texture_cache/image_view_base.h" -#include "video_core/texture_cache/image_view_info.h" -#include "video_core/texture_cache/render_targets.h" #include "video_core/texture_cache/samples_helper.h" -#include "video_core/texture_cache/slot_vector.h" -#include "video_core/texture_cache/types.h" -#include "video_core/texture_cache/util.h" -#include "video_core/textures/texture.h" +#include "video_core/texture_cache/texture_cache_base.h" namespace VideoCommon { @@ -62,352 +25,6 @@ using VideoCore::Surface::SurfaceType; using namespace Common::Literals; template <class P> -class TextureCache { - /// Address shift for caching images into a hash table - static constexpr u64 PAGE_BITS = 20; - - /// Enables debugging features to the texture cache - static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; - /// Implement blits as copies between framebuffers - static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS; - /// True when some copies have to be emulated - static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; - /// True when the API can provide info about the memory of the device. - static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO; - - /// Image view ID for null descriptors - static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0}; - /// Sampler ID for bugged sampler ids - static constexpr SamplerId NULL_SAMPLER_ID{0}; - - static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB; - static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB; - - using Runtime = typename P::Runtime; - using Image = typename P::Image; - using ImageAlloc = typename P::ImageAlloc; - using ImageView = typename P::ImageView; - using Sampler = typename P::Sampler; - using Framebuffer = typename P::Framebuffer; - - struct BlitImages { - ImageId dst_id; - ImageId src_id; - PixelFormat dst_format; - PixelFormat src_format; - }; - - template <typename T> - struct IdentityHash { - [[nodiscard]] size_t operator()(T value) const noexcept { - return static_cast<size_t>(value); - } - }; - -public: - explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&, - Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&); - - /// Notify the cache that a new frame has been queued - void TickFrame(); - - /// Return a constant reference to the given image view id - [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept; - - /// Return a reference to the given image view id - [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept; - - /// Mark an image as modified from the GPU - void MarkModification(ImageId id) noexcept; - - /// Fill image_view_ids with the graphics images in indices - void FillGraphicsImageViews(std::span<const u32> indices, - std::span<ImageViewId> image_view_ids); - - /// Fill image_view_ids with the compute images in indices - void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids); - - /// Get the sampler from the graphics descriptor table in the specified index - Sampler* GetGraphicsSampler(u32 index); - - /// Get the sampler from the compute descriptor table in the specified index - Sampler* GetComputeSampler(u32 index); - - /// Refresh the state for graphics image view and sampler descriptors - void SynchronizeGraphicsDescriptors(); - - /// Refresh the state for compute image view and sampler descriptors - void SynchronizeComputeDescriptors(); - - /// Update bound render targets and upload memory if necessary - /// @param is_clear True when the render targets are being used for clears - void UpdateRenderTargets(bool is_clear); - - /// Find a framebuffer with the currently bound render targets - /// UpdateRenderTargets should be called before this - Framebuffer* GetFramebuffer(); - - /// Mark images in a range as modified from the CPU - void WriteMemory(VAddr cpu_addr, size_t size); - - /// Download contents of host images to guest memory in a region - void DownloadMemory(VAddr cpu_addr, size_t size); - - /// Remove images in a region - void UnmapMemory(VAddr cpu_addr, size_t size); - - /// Remove images in a region - void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size); - - /// Blit an image with the given parameters - void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, - const Tegra::Engines::Fermi2D::Surface& src, - const Tegra::Engines::Fermi2D::Config& copy); - - /// Invalidate the contents of the color buffer index - /// These contents become unspecified, the cache can assume aggressive optimizations. - void InvalidateColorBuffer(size_t index); - - /// Invalidate the contents of the depth buffer - /// These contents become unspecified, the cache can assume aggressive optimizations. - void InvalidateDepthBuffer(); - - /// Try to find a cached image view in the given CPU address - [[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr); - - /// Return true when there are uncommitted images to be downloaded - [[nodiscard]] bool HasUncommittedFlushes() const noexcept; - - /// Return true when the caller should wait for async downloads - [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept; - - /// Commit asynchronous downloads - void CommitAsyncFlushes(); - - /// Pop asynchronous downloads - void PopAsyncFlushes(); - - /// Return true when a CPU region is modified from the GPU - [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); - - std::mutex mutex; - -private: - /// Iterate over all page indices in a range - template <typename Func> - static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) { - static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; - const u64 page_end = (addr + size - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { - if constexpr (RETURNS_BOOL) { - if (func(page)) { - break; - } - } else { - func(page); - } - } - } - - template <typename Func> - static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) { - static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; - const u64 page_end = (addr + size - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { - if constexpr (RETURNS_BOOL) { - if (func(page)) { - break; - } - } else { - func(page); - } - } - } - - /// Runs the Garbage Collector. - void RunGarbageCollector(); - - /// Fills image_view_ids in the image views in indices - void FillImageViews(DescriptorTable<TICEntry>& table, - std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices, - std::span<ImageViewId> image_view_ids); - - /// Find or create an image view in the guest descriptor table - ImageViewId VisitImageView(DescriptorTable<TICEntry>& table, - std::span<ImageViewId> cached_image_view_ids, u32 index); - - /// Find or create a framebuffer with the given render target parameters - FramebufferId GetFramebufferId(const RenderTargets& key); - - /// Refresh the contents (pixel data) of an image - void RefreshContents(Image& image, ImageId image_id); - - /// Upload data from guest to an image - template <typename StagingBuffer> - void UploadImageContents(Image& image, StagingBuffer& staging_buffer); - - /// Find or create an image view from a guest descriptor - [[nodiscard]] ImageViewId FindImageView(const TICEntry& config); - - /// Create a new image view from a guest descriptor - [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config); - - /// Find or create an image from the given parameters - [[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, - RelaxedOptions options = RelaxedOptions{}); - - /// Find an image from the given parameters - [[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr, - RelaxedOptions options); - - /// Create an image from the given parameters - [[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, - RelaxedOptions options); - - /// Create a new image and join perfectly matching existing images - /// Remove joined images from the cache - [[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); - - /// Return a blit image pair from the given guest blit parameters - [[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst, - const Tegra::Engines::Fermi2D::Surface& src); - - /// Find or create a sampler from a guest descriptor sampler - [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); - - /// Find or create an image view for the given color buffer index - [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); - - /// Find or create an image view for the depth buffer - [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); - - /// Find or create a view for a render target with the given image parameters - [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, - bool is_clear); - - /// Iterates over all the images in a region calling func - template <typename Func> - void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func); - - template <typename Func> - void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func); - - template <typename Func> - void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func); - - /// Iterates over all the images in a region calling func - template <typename Func> - void ForEachSparseSegment(ImageBase& image, Func&& func); - - /// Find or create an image view in the given image with the passed parameters - [[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info); - - /// Register image in the page table - void RegisterImage(ImageId image); - - /// Unregister image from the page table - void UnregisterImage(ImageId image); - - /// Track CPU reads and writes for image - void TrackImage(ImageBase& image, ImageId image_id); - - /// Stop tracking CPU reads and writes for image - void UntrackImage(ImageBase& image, ImageId image_id); - - /// Delete image from the cache - void DeleteImage(ImageId image); - - /// Remove image views references from the cache - void RemoveImageViewReferences(std::span<const ImageViewId> removed_views); - - /// Remove framebuffers using the given image views from the cache - void RemoveFramebuffers(std::span<const ImageViewId> removed_views); - - /// Mark an image as modified from the GPU - void MarkModification(ImageBase& image) noexcept; - - /// Synchronize image aliases, copying data if needed - void SynchronizeAliases(ImageId image_id); - - /// Prepare an image to be used - void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); - - /// Prepare an image view to be used - void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); - - /// Execute copies from one image to the other, even if they are incompatible - void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies); - - /// Bind an image view as render target, downloading resources preemtively if needed - void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id); - - /// Create a render target from a given image and image view parameters - [[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage( - ImageId, const ImageViewInfo& view_info); - - /// Returns true if the current clear parameters clear the whole image of a given image view - [[nodiscard]] bool IsFullClear(ImageViewId id); - - Runtime& runtime; - VideoCore::RasterizerInterface& rasterizer; - Tegra::Engines::Maxwell3D& maxwell3d; - Tegra::Engines::KeplerCompute& kepler_compute; - Tegra::MemoryManager& gpu_memory; - - DescriptorTable<TICEntry> graphics_image_table{gpu_memory}; - DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory}; - std::vector<SamplerId> graphics_sampler_ids; - std::vector<ImageViewId> graphics_image_view_ids; - - DescriptorTable<TICEntry> compute_image_table{gpu_memory}; - DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory}; - std::vector<SamplerId> compute_sampler_ids; - std::vector<ImageViewId> compute_image_view_ids; - - RenderTargets render_targets; - - std::unordered_map<TICEntry, ImageViewId> image_views; - std::unordered_map<TSCEntry, SamplerId> samplers; - std::unordered_map<RenderTargets, FramebufferId> framebuffers; - - std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table; - std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table; - std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table; - - std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views; - - VAddr virtual_invalid_space{}; - - bool has_deleted_images = false; - u64 total_used_memory = 0; - u64 minimum_memory; - u64 expected_memory; - u64 critical_memory; - - SlotVector<Image> slot_images; - SlotVector<ImageMapView> slot_map_views; - SlotVector<ImageView> slot_image_views; - SlotVector<ImageAlloc> slot_image_allocs; - SlotVector<Sampler> slot_samplers; - SlotVector<Framebuffer> slot_framebuffers; - - // TODO: This data structure is not optimal and it should be reworked - std::vector<ImageId> uncommitted_downloads; - std::queue<std::vector<ImageId>> committed_downloads; - - static constexpr size_t TICKS_TO_DESTROY = 6; - DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images; - DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view; - DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers; - - std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table; - - u64 modification_tick = 0; - u64 frame_tick = 0; - typename SlotVector<Image>::Iterator deletion_iterator; -}; - -template <class P> TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::Engines::KeplerCompute& kepler_compute_, @@ -821,40 +438,6 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, } template <class P> -void TextureCache<P>::InvalidateColorBuffer(size_t index) { - ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; - color_buffer_id = FindColorBuffer(index, false); - if (!color_buffer_id) { - LOG_ERROR(HW_GPU, "Invalidating invalid color buffer in index={}", index); - return; - } - // When invalidating a color buffer, the old contents are no longer relevant - ImageView& color_buffer = slot_image_views[color_buffer_id]; - Image& image = slot_images[color_buffer.image_id]; - image.flags &= ~ImageFlagBits::CpuModified; - image.flags &= ~ImageFlagBits::GpuModified; - - runtime.InvalidateColorBuffer(color_buffer, index); -} - -template <class P> -void TextureCache<P>::InvalidateDepthBuffer() { - ImageViewId& depth_buffer_id = render_targets.depth_buffer_id; - depth_buffer_id = FindDepthBuffer(false); - if (!depth_buffer_id) { - LOG_ERROR(HW_GPU, "Invalidating invalid depth buffer"); - return; - } - // When invalidating the depth buffer, the old contents are no longer relevant - ImageBase& image = slot_images[slot_image_views[depth_buffer_id].image_id]; - image.flags &= ~ImageFlagBits::CpuModified; - image.flags &= ~ImageFlagBits::GpuModified; - - ImageView& depth_buffer = slot_image_views[depth_buffer_id]; - runtime.InvalidateDepthBuffer(depth_buffer); -} - -template <class P> typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) { // TODO: Properly implement this const auto it = page_table.find(cpu_addr >> PAGE_BITS); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h new file mode 100644 index 000000000..e4ae351cb --- /dev/null +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -0,0 +1,385 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <mutex> +#include <span> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "common/common_types.h" +#include "common/literals.h" +#include "video_core/compatible_formats.h" +#include "video_core/delayed_destruction_ring.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/descriptor_table.h" +#include "video_core/texture_cache/image_base.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/render_targets.h" +#include "video_core/texture_cache/slot_vector.h" +#include "video_core/texture_cache/types.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using Tegra::Texture::SwizzleSource; +using Tegra::Texture::TICEntry; +using Tegra::Texture::TSCEntry; +using VideoCore::Surface::GetFormatType; +using VideoCore::Surface::IsCopyCompatible; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using namespace Common::Literals; + +template <class P> +class TextureCache { + /// Address shift for caching images into a hash table + static constexpr u64 PAGE_BITS = 20; + + /// Enables debugging features to the texture cache + static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; + /// Implement blits as copies between framebuffers + static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS; + /// True when some copies have to be emulated + static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; + /// True when the API can provide info about the memory of the device. + static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO; + + /// Image view ID for null descriptors + static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0}; + /// Sampler ID for bugged sampler ids + static constexpr SamplerId NULL_SAMPLER_ID{0}; + + static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB; + static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB; + + using Runtime = typename P::Runtime; + using Image = typename P::Image; + using ImageAlloc = typename P::ImageAlloc; + using ImageView = typename P::ImageView; + using Sampler = typename P::Sampler; + using Framebuffer = typename P::Framebuffer; + + struct BlitImages { + ImageId dst_id; + ImageId src_id; + PixelFormat dst_format; + PixelFormat src_format; + }; + + template <typename T> + struct IdentityHash { + [[nodiscard]] size_t operator()(T value) const noexcept { + return static_cast<size_t>(value); + } + }; + +public: + explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&, + Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&); + + /// Notify the cache that a new frame has been queued + void TickFrame(); + + /// Return a constant reference to the given image view id + [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept; + + /// Return a reference to the given image view id + [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept; + + /// Mark an image as modified from the GPU + void MarkModification(ImageId id) noexcept; + + /// Fill image_view_ids with the graphics images in indices + void FillGraphicsImageViews(std::span<const u32> indices, + std::span<ImageViewId> image_view_ids); + + /// Fill image_view_ids with the compute images in indices + void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids); + + /// Get the sampler from the graphics descriptor table in the specified index + Sampler* GetGraphicsSampler(u32 index); + + /// Get the sampler from the compute descriptor table in the specified index + Sampler* GetComputeSampler(u32 index); + + /// Refresh the state for graphics image view and sampler descriptors + void SynchronizeGraphicsDescriptors(); + + /// Refresh the state for compute image view and sampler descriptors + void SynchronizeComputeDescriptors(); + + /// Update bound render targets and upload memory if necessary + /// @param is_clear True when the render targets are being used for clears + void UpdateRenderTargets(bool is_clear); + + /// Find a framebuffer with the currently bound render targets + /// UpdateRenderTargets should be called before this + Framebuffer* GetFramebuffer(); + + /// Mark images in a range as modified from the CPU + void WriteMemory(VAddr cpu_addr, size_t size); + + /// Download contents of host images to guest memory in a region + void DownloadMemory(VAddr cpu_addr, size_t size); + + /// Remove images in a region + void UnmapMemory(VAddr cpu_addr, size_t size); + + /// Remove images in a region + void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size); + + /// Blit an image with the given parameters + void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Config& copy); + + /// Try to find a cached image view in the given CPU address + [[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr); + + /// Return true when there are uncommitted images to be downloaded + [[nodiscard]] bool HasUncommittedFlushes() const noexcept; + + /// Return true when the caller should wait for async downloads + [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept; + + /// Commit asynchronous downloads + void CommitAsyncFlushes(); + + /// Pop asynchronous downloads + void PopAsyncFlushes(); + + /// Return true when a CPU region is modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); + + std::mutex mutex; + +private: + /// Iterate over all page indices in a range + template <typename Func> + static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; + const u64 page_end = (addr + size - 1) >> PAGE_BITS; + for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); + } + } + } + + template <typename Func> + static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; + const u64 page_end = (addr + size - 1) >> PAGE_BITS; + for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); + } + } + } + + /// Runs the Garbage Collector. + void RunGarbageCollector(); + + /// Fills image_view_ids in the image views in indices + void FillImageViews(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices, + std::span<ImageViewId> image_view_ids); + + /// Find or create an image view in the guest descriptor table + ImageViewId VisitImageView(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, u32 index); + + /// Find or create a framebuffer with the given render target parameters + FramebufferId GetFramebufferId(const RenderTargets& key); + + /// Refresh the contents (pixel data) of an image + void RefreshContents(Image& image, ImageId image_id); + + /// Upload data from guest to an image + template <typename StagingBuffer> + void UploadImageContents(Image& image, StagingBuffer& staging_buffer); + + /// Find or create an image view from a guest descriptor + [[nodiscard]] ImageViewId FindImageView(const TICEntry& config); + + /// Create a new image view from a guest descriptor + [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config); + + /// Find or create an image from the given parameters + [[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options = RelaxedOptions{}); + + /// Find an image from the given parameters + [[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options); + + /// Create an image from the given parameters + [[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options); + + /// Create a new image and join perfectly matching existing images + /// Remove joined images from the cache + [[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); + + /// Return a blit image pair from the given guest blit parameters + [[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Surface& src); + + /// Find or create a sampler from a guest descriptor sampler + [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); + + /// Find or create an image view for the given color buffer index + [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); + + /// Find or create an image view for the depth buffer + [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); + + /// Find or create a view for a render target with the given image parameters + [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, + bool is_clear); + + /// Iterates over all the images in a region calling func + template <typename Func> + void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func); + + template <typename Func> + void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func); + + template <typename Func> + void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func); + + /// Iterates over all the images in a region calling func + template <typename Func> + void ForEachSparseSegment(ImageBase& image, Func&& func); + + /// Find or create an image view in the given image with the passed parameters + [[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info); + + /// Register image in the page table + void RegisterImage(ImageId image); + + /// Unregister image from the page table + void UnregisterImage(ImageId image); + + /// Track CPU reads and writes for image + void TrackImage(ImageBase& image, ImageId image_id); + + /// Stop tracking CPU reads and writes for image + void UntrackImage(ImageBase& image, ImageId image_id); + + /// Delete image from the cache + void DeleteImage(ImageId image); + + /// Remove image views references from the cache + void RemoveImageViewReferences(std::span<const ImageViewId> removed_views); + + /// Remove framebuffers using the given image views from the cache + void RemoveFramebuffers(std::span<const ImageViewId> removed_views); + + /// Mark an image as modified from the GPU + void MarkModification(ImageBase& image) noexcept; + + /// Synchronize image aliases, copying data if needed + void SynchronizeAliases(ImageId image_id); + + /// Prepare an image to be used + void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); + + /// Prepare an image view to be used + void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); + + /// Execute copies from one image to the other, even if they are incompatible + void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies); + + /// Bind an image view as render target, downloading resources preemtively if needed + void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id); + + /// Create a render target from a given image and image view parameters + [[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage( + ImageId, const ImageViewInfo& view_info); + + /// Returns true if the current clear parameters clear the whole image of a given image view + [[nodiscard]] bool IsFullClear(ImageViewId id); + + Runtime& runtime; + VideoCore::RasterizerInterface& rasterizer; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; + + DescriptorTable<TICEntry> graphics_image_table{gpu_memory}; + DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory}; + std::vector<SamplerId> graphics_sampler_ids; + std::vector<ImageViewId> graphics_image_view_ids; + + DescriptorTable<TICEntry> compute_image_table{gpu_memory}; + DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory}; + std::vector<SamplerId> compute_sampler_ids; + std::vector<ImageViewId> compute_image_view_ids; + + RenderTargets render_targets; + + std::unordered_map<TICEntry, ImageViewId> image_views; + std::unordered_map<TSCEntry, SamplerId> samplers; + std::unordered_map<RenderTargets, FramebufferId> framebuffers; + + std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table; + std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table; + std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table; + + std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views; + + VAddr virtual_invalid_space{}; + + bool has_deleted_images = false; + u64 total_used_memory = 0; + u64 minimum_memory; + u64 expected_memory; + u64 critical_memory; + + SlotVector<Image> slot_images; + SlotVector<ImageMapView> slot_map_views; + SlotVector<ImageView> slot_image_views; + SlotVector<ImageAlloc> slot_image_allocs; + SlotVector<Sampler> slot_samplers; + SlotVector<Framebuffer> slot_framebuffers; + + // TODO: This data structure is not optimal and it should be reworked + std::vector<ImageId> uncommitted_downloads; + std::queue<std::vector<ImageId>> committed_downloads; + + static constexpr size_t TICKS_TO_DESTROY = 6; + DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images; + DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view; + DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers; + + std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table; + + u64 modification_tick = 0; + u64 frame_tick = 0; + typename SlotVector<Image>::Iterator deletion_iterator; +}; + +} // namespace VideoCommon diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index 3ab500760..25161df1f 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -151,6 +151,76 @@ private: const IntType& m_Bits; }; +enum class IntegerEncoding { JustBits, Quint, Trit }; + +struct IntegerEncodedValue { + constexpr IntegerEncodedValue() = default; + + constexpr IntegerEncodedValue(IntegerEncoding encoding_, u32 num_bits_) + : encoding{encoding_}, num_bits{num_bits_} {} + + constexpr bool MatchesEncoding(const IntegerEncodedValue& other) const { + return encoding == other.encoding && num_bits == other.num_bits; + } + + // Returns the number of bits required to encode num_vals values. + u32 GetBitLength(u32 num_vals) const { + u32 total_bits = num_bits * num_vals; + if (encoding == IntegerEncoding::Trit) { + total_bits += (num_vals * 8 + 4) / 5; + } else if (encoding == IntegerEncoding::Quint) { + total_bits += (num_vals * 7 + 2) / 3; + } + return total_bits; + } + + IntegerEncoding encoding{}; + u32 num_bits = 0; + u32 bit_value = 0; + union { + u32 quint_value = 0; + u32 trit_value; + }; +}; + +// Returns a new instance of this struct that corresponds to the +// can take no more than mav_value values +static constexpr IntegerEncodedValue CreateEncoding(u32 mav_value) { + while (mav_value > 0) { + u32 check = mav_value + 1; + + // Is mav_value a power of two? + if (!(check & (check - 1))) { + return IntegerEncodedValue(IntegerEncoding::JustBits, std::popcount(mav_value)); + } + + // Is mav_value of the type 3*2^n - 1? + if ((check % 3 == 0) && !((check / 3) & ((check / 3) - 1))) { + return IntegerEncodedValue(IntegerEncoding::Trit, std::popcount(check / 3 - 1)); + } + + // Is mav_value of the type 5*2^n - 1? + if ((check % 5 == 0) && !((check / 5) & ((check / 5) - 1))) { + return IntegerEncodedValue(IntegerEncoding::Quint, std::popcount(check / 5 - 1)); + } + + // Apparently it can't be represented with a bounded integer sequence... + // just iterate. + mav_value--; + } + return IntegerEncodedValue(IntegerEncoding::JustBits, 0); +} + +static constexpr std::array<IntegerEncodedValue, 256> MakeEncodedValues() { + std::array<IntegerEncodedValue, 256> encodings{}; + for (std::size_t i = 0; i < encodings.size(); ++i) { + encodings[i] = CreateEncoding(static_cast<u32>(i)); + } + return encodings; +} + +static constexpr std::array<IntegerEncodedValue, 256> ASTC_ENCODINGS_VALUES = MakeEncodedValues(); + namespace Tegra::Texture::ASTC { using IntegerEncodedVector = boost::container::static_vector< IntegerEncodedValue, 256, @@ -521,35 +591,41 @@ static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) { return params; } -static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth, - u32 blockHeight) { - // Don't actually care about the void extent, just read the bits... - for (s32 i = 0; i < 4; ++i) { - strm.ReadBits<13>(); +// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] +// is the same as [(num_bits - 1):0] and repeats all the way down. +template <typename IntType> +static constexpr IntType Replicate(IntType val, u32 num_bits, u32 to_bit) { + if (num_bits == 0 || to_bit == 0) { + return 0; } - - // Decode the RGBA components and renormalize them to the range [0, 255] - u16 r = static_cast<u16>(strm.ReadBits<16>()); - u16 g = static_cast<u16>(strm.ReadBits<16>()); - u16 b = static_cast<u16>(strm.ReadBits<16>()); - u16 a = static_cast<u16>(strm.ReadBits<16>()); - - u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 | - (static_cast<u32>(a) & 0xFF00) << 16; - - for (u32 j = 0; j < blockHeight; j++) { - for (u32 i = 0; i < blockWidth; i++) { - outBuf[j * blockWidth + i] = rgba; + const IntType v = val & static_cast<IntType>((1 << num_bits) - 1); + IntType res = v; + u32 reslen = num_bits; + while (reslen < to_bit) { + u32 comp = 0; + if (num_bits > to_bit - reslen) { + u32 newshift = to_bit - reslen; + comp = num_bits - newshift; + num_bits = newshift; } + res = static_cast<IntType>(res << num_bits); + res = static_cast<IntType>(res | (v >> comp)); + reslen += num_bits; } + return res; } -static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { - for (u32 j = 0; j < blockHeight; j++) { - for (u32 i = 0; i < blockWidth; i++) { - outBuf[j * blockWidth + i] = 0xFFFF00FF; - } +static constexpr std::size_t NumReplicateEntries(u32 num_bits) { + return std::size_t(1) << num_bits; +} + +template <typename IntType, u32 num_bits, u32 to_bit> +static constexpr auto MakeReplicateTable() { + std::array<IntType, NumReplicateEntries(num_bits)> table{}; + for (IntType value = 0; value < static_cast<IntType>(std::size(table)); ++value) { + table[value] = Replicate(value, num_bits, to_bit); } + return table; } static constexpr auto REPLICATE_BYTE_TO_16_TABLE = MakeReplicateTable<u32, 8, 16>(); @@ -572,6 +648,9 @@ static constexpr auto REPLICATE_2_BIT_TO_8_TABLE = MakeReplicateTable<u32, 2, 8> static constexpr auto REPLICATE_3_BIT_TO_8_TABLE = MakeReplicateTable<u32, 3, 8>(); static constexpr auto REPLICATE_4_BIT_TO_8_TABLE = MakeReplicateTable<u32, 4, 8>(); static constexpr auto REPLICATE_5_BIT_TO_8_TABLE = MakeReplicateTable<u32, 5, 8>(); +static constexpr auto REPLICATE_6_BIT_TO_8_TABLE = MakeReplicateTable<u32, 6, 8>(); +static constexpr auto REPLICATE_7_BIT_TO_8_TABLE = MakeReplicateTable<u32, 7, 8>(); +static constexpr auto REPLICATE_8_BIT_TO_8_TABLE = MakeReplicateTable<u32, 8, 8>(); /// Use a precompiled table with the most common usages, if it's not in the expected range, fallback /// to the runtime implementation static constexpr u32 FastReplicateTo8(u32 value, u32 num_bits) { @@ -1316,6 +1395,37 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, #undef READ_INT_VALUES } +static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth, + u32 blockHeight) { + // Don't actually care about the void extent, just read the bits... + for (s32 i = 0; i < 4; ++i) { + strm.ReadBits<13>(); + } + + // Decode the RGBA components and renormalize them to the range [0, 255] + u16 r = static_cast<u16>(strm.ReadBits<16>()); + u16 g = static_cast<u16>(strm.ReadBits<16>()); + u16 b = static_cast<u16>(strm.ReadBits<16>()); + u16 a = static_cast<u16>(strm.ReadBits<16>()); + + u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 | + (static_cast<u32>(a) & 0xFF00) << 16; + + for (u32 j = 0; j < blockHeight; j++) { + for (u32 i = 0; i < blockWidth; i++) { + outBuf[j * blockWidth + i] = rgba; + } + } +} + +static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { + for (u32 j = 0; j < blockHeight; j++) { + for (u32 i = 0; i < blockWidth; i++) { + outBuf[j * blockWidth + i] = 0xFFFF00FF; + } + } +} + static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth, const u32 blockHeight, std::span<u32, 12 * 12> outBuf) { InputBitStream strm(inBuf); diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h index 0229ae122..14d2beec0 100644 --- a/src/video_core/textures/astc.h +++ b/src/video_core/textures/astc.h @@ -9,117 +9,6 @@ namespace Tegra::Texture::ASTC { -enum class IntegerEncoding { JustBits, Quint, Trit }; - -struct IntegerEncodedValue { - constexpr IntegerEncodedValue() = default; - - constexpr IntegerEncodedValue(IntegerEncoding encoding_, u32 num_bits_) - : encoding{encoding_}, num_bits{num_bits_} {} - - constexpr bool MatchesEncoding(const IntegerEncodedValue& other) const { - return encoding == other.encoding && num_bits == other.num_bits; - } - - // Returns the number of bits required to encode num_vals values. - u32 GetBitLength(u32 num_vals) const { - u32 total_bits = num_bits * num_vals; - if (encoding == IntegerEncoding::Trit) { - total_bits += (num_vals * 8 + 4) / 5; - } else if (encoding == IntegerEncoding::Quint) { - total_bits += (num_vals * 7 + 2) / 3; - } - return total_bits; - } - - IntegerEncoding encoding{}; - u32 num_bits = 0; - u32 bit_value = 0; - union { - u32 quint_value = 0; - u32 trit_value; - }; -}; - -// Returns a new instance of this struct that corresponds to the -// can take no more than mav_value values -constexpr IntegerEncodedValue CreateEncoding(u32 mav_value) { - while (mav_value > 0) { - u32 check = mav_value + 1; - - // Is mav_value a power of two? - if (!(check & (check - 1))) { - return IntegerEncodedValue(IntegerEncoding::JustBits, std::popcount(mav_value)); - } - - // Is mav_value of the type 3*2^n - 1? - if ((check % 3 == 0) && !((check / 3) & ((check / 3) - 1))) { - return IntegerEncodedValue(IntegerEncoding::Trit, std::popcount(check / 3 - 1)); - } - - // Is mav_value of the type 5*2^n - 1? - if ((check % 5 == 0) && !((check / 5) & ((check / 5) - 1))) { - return IntegerEncodedValue(IntegerEncoding::Quint, std::popcount(check / 5 - 1)); - } - - // Apparently it can't be represented with a bounded integer sequence... - // just iterate. - mav_value--; - } - return IntegerEncodedValue(IntegerEncoding::JustBits, 0); -} - -constexpr std::array<IntegerEncodedValue, 256> MakeEncodedValues() { - std::array<IntegerEncodedValue, 256> encodings{}; - for (std::size_t i = 0; i < encodings.size(); ++i) { - encodings[i] = CreateEncoding(static_cast<u32>(i)); - } - return encodings; -} - -constexpr std::array<IntegerEncodedValue, 256> ASTC_ENCODINGS_VALUES = MakeEncodedValues(); - -// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] -// is the same as [(num_bits - 1):0] and repeats all the way down. -template <typename IntType> -constexpr IntType Replicate(IntType val, u32 num_bits, u32 to_bit) { - if (num_bits == 0 || to_bit == 0) { - return 0; - } - const IntType v = val & static_cast<IntType>((1 << num_bits) - 1); - IntType res = v; - u32 reslen = num_bits; - while (reslen < to_bit) { - u32 comp = 0; - if (num_bits > to_bit - reslen) { - u32 newshift = to_bit - reslen; - comp = num_bits - newshift; - num_bits = newshift; - } - res = static_cast<IntType>(res << num_bits); - res = static_cast<IntType>(res | (v >> comp)); - reslen += num_bits; - } - return res; -} - -constexpr std::size_t NumReplicateEntries(u32 num_bits) { - return std::size_t(1) << num_bits; -} - -template <typename IntType, u32 num_bits, u32 to_bit> -constexpr auto MakeReplicateTable() { - std::array<IntType, NumReplicateEntries(num_bits)> table{}; - for (IntType value = 0; value < static_cast<IntType>(std::size(table)); ++value) { - table[value] = Replicate(value, num_bits, to_bit); - } - return table; -} - -constexpr auto REPLICATE_6_BIT_TO_8_TABLE = MakeReplicateTable<u32, 6, 8>(); -constexpr auto REPLICATE_7_BIT_TO_8_TABLE = MakeReplicateTable<u32, 7, 8>(); -constexpr auto REPLICATE_8_BIT_TO_8_TABLE = MakeReplicateTable<u32, 8, 8>(); - void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, uint32_t block_width, uint32_t block_height, std::span<uint8_t> output); diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index f1f523ad1..c010b9353 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -18,9 +18,9 @@ namespace Tegra::Texture { namespace { -template <bool TO_LINEAR> -void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, - u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { +template <bool TO_LINEAR, u32 BYTES_PER_PIXEL> +void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth, u32 stride_alignment) { // The origin of the transformation can be configured here, leave it as zero as the current API // doesn't expose it. static constexpr u32 origin_x = 0; @@ -28,9 +28,9 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe static constexpr u32 origin_z = 0; // We can configure here a custom pitch - // As it's not exposed 'width * bpp' will be the expected pitch. - const u32 pitch = width * bytes_per_pixel; - const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; + // As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch. + const u32 pitch = width * BYTES_PER_PIXEL; + const u32 stride = Common::AlignUpLog2(width, stride_alignment) * BYTES_PER_PIXEL; const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); @@ -54,14 +54,14 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe ((block_y & block_height_mask) << GOB_SIZE_SHIFT); for (u32 column = 0; column < width; ++column) { - const u32 x = (column + origin_x) * bytes_per_pixel; + const u32 x = (column + origin_x) * BYTES_PER_PIXEL; const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; const u32 base_swizzled_offset = offset_z + offset_y + offset_x; const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; const u32 unswizzled_offset = - slice * pitch * height + line * pitch + column * bytes_per_pixel; + slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; if (const auto offset = (TO_LINEAR ? unswizzled_offset : swizzled_offset); offset >= input.size()) { @@ -73,33 +73,42 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; - std::memcpy(dst, src, bytes_per_pixel); + + std::memcpy(dst, src, BYTES_PER_PIXEL); } } } } -} // Anonymous namespace -void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, - u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, - u32 stride_alignment) { - Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, - stride_alignment); -} - -void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, - u32 height, u32 depth, u32 block_height, u32 block_depth, - u32 stride_alignment) { - Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, - stride_alignment); +template <bool TO_LINEAR> +void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { + switch (bytes_per_pixel) { +#define BPP_CASE(x) \ + case x: \ + return SwizzleImpl<TO_LINEAR, x>(output, input, width, height, depth, block_height, \ + block_depth, stride_alignment); + BPP_CASE(1) + BPP_CASE(2) + BPP_CASE(3) + BPP_CASE(4) + BPP_CASE(6) + BPP_CASE(8) + BPP_CASE(12) + BPP_CASE(16) +#undef BPP_CASE + default: + UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + } } +template <u32 BYTES_PER_PIXEL> void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data, - u32 block_height_bit, u32 offset_x, u32 offset_y) { + u8* swizzled_data, const u8* unswizzled_data, u32 block_height_bit, + u32 offset_x, u32 offset_y) { const u32 block_height = 1U << block_height_bit; const u32 image_width_in_gobs = - (swizzled_width * bytes_per_pixel + (GOB_SIZE_X - 1)) / GOB_SIZE_X; + (swizzled_width * BYTES_PER_PIXEL + (GOB_SIZE_X - 1)) / GOB_SIZE_X; for (u32 line = 0; line < subrect_height; ++line) { const u32 dst_y = line + offset_y; const u32 gob_address_y = @@ -109,20 +118,21 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 for (u32 x = 0; x < subrect_width; ++x) { const u32 dst_x = x + offset_x; const u32 gob_address = - gob_address_y + (dst_x * bytes_per_pixel / GOB_SIZE_X) * GOB_SIZE * block_height; - const u32 swizzled_offset = gob_address + table[(dst_x * bytes_per_pixel) % GOB_SIZE_X]; - const u32 unswizzled_offset = line * source_pitch + x * bytes_per_pixel; + gob_address_y + (dst_x * BYTES_PER_PIXEL / GOB_SIZE_X) * GOB_SIZE * block_height; + const u32 swizzled_offset = gob_address + table[(dst_x * BYTES_PER_PIXEL) % GOB_SIZE_X]; + const u32 unswizzled_offset = line * source_pitch + x * BYTES_PER_PIXEL; const u8* const source_line = unswizzled_data + unswizzled_offset; u8* const dest_addr = swizzled_data + swizzled_offset; - std::memcpy(dest_addr, source_line, bytes_per_pixel); + std::memcpy(dest_addr, source_line, BYTES_PER_PIXEL); } } } -void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel, - u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) { - const u32 stride = width * bytes_per_pixel; +template <u32 BYTES_PER_PIXEL> +void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 block_height, + u32 origin_x, u32 origin_y, u8* output, const u8* input) { + const u32 stride = width * BYTES_PER_PIXEL; const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X; const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height); @@ -137,24 +147,25 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, const u32 src_offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); for (u32 column = 0; column < line_length_in; ++column) { - const u32 src_x = (column + origin_x) * bytes_per_pixel; + const u32 src_x = (column + origin_x) * BYTES_PER_PIXEL; const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift; const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X]; - const u32 unswizzled_offset = line * pitch + column * bytes_per_pixel; + const u32 unswizzled_offset = line * pitch + column * BYTES_PER_PIXEL; - std::memcpy(output + unswizzled_offset, input + swizzled_offset, bytes_per_pixel); + std::memcpy(output + unswizzled_offset, input + swizzled_offset, BYTES_PER_PIXEL); } } } +template <u32 BYTES_PER_PIXEL> void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height, - u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x, - u32 origin_y, u8* output, const u8* input) { + u32 block_height, u32 block_depth, u32 origin_x, u32 origin_y, u8* output, + const u8* input) { UNIMPLEMENTED_IF(origin_x > 0); UNIMPLEMENTED_IF(origin_y > 0); - const u32 stride = width * bytes_per_pixel; + const u32 stride = width * BYTES_PER_PIXEL; const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X; const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); @@ -169,11 +180,93 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt for (u32 x = 0; x < line_length_in; ++x) { const u32 dst_offset = ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + table[x % GOB_SIZE_X]; - const u32 src_offset = x * bytes_per_pixel + line * pitch; - std::memcpy(output + dst_offset, input + src_offset, bytes_per_pixel); + const u32 src_offset = x * BYTES_PER_PIXEL + line * pitch; + std::memcpy(output + dst_offset, input + src_offset, BYTES_PER_PIXEL); } } } +} // Anonymous namespace + +void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { + Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride_alignment); +} + +void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { + Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride_alignment); +} + +void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, + u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data, + u32 block_height_bit, u32 offset_x, u32 offset_y) { + switch (bytes_per_pixel) { +#define BPP_CASE(x) \ + case x: \ + return SwizzleSubrect<x>(subrect_width, subrect_height, source_pitch, swizzled_width, \ + swizzled_data, unswizzled_data, block_height_bit, offset_x, \ + offset_y); + BPP_CASE(1) + BPP_CASE(2) + BPP_CASE(3) + BPP_CASE(4) + BPP_CASE(6) + BPP_CASE(8) + BPP_CASE(12) + BPP_CASE(16) +#undef BPP_CASE + default: + UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + } +} + +void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel, + u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) { + switch (bytes_per_pixel) { +#define BPP_CASE(x) \ + case x: \ + return UnswizzleSubrect<x>(line_length_in, line_count, pitch, width, block_height, \ + origin_x, origin_y, output, input); + BPP_CASE(1) + BPP_CASE(2) + BPP_CASE(3) + BPP_CASE(4) + BPP_CASE(6) + BPP_CASE(8) + BPP_CASE(12) + BPP_CASE(16) +#undef BPP_CASE + default: + UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + } +} + +void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height, + u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x, + u32 origin_y, u8* output, const u8* input) { + switch (bytes_per_pixel) { +#define BPP_CASE(x) \ + case x: \ + return SwizzleSliceToVoxel<x>(line_length_in, line_count, pitch, width, height, \ + block_height, block_depth, origin_x, origin_y, output, \ + input); + BPP_CASE(1) + BPP_CASE(2) + BPP_CASE(3) + BPP_CASE(4) + BPP_CASE(6) + BPP_CASE(8) + BPP_CASE(12) + BPP_CASE(16) +#undef BPP_CASE + default: + UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + } +} void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y, const u32 block_height_bit, const std::size_t copy_size, const u8* source_data, @@ -194,7 +287,7 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 u8* dest_addr = swizzle_data + swizzled_offset; count++; - std::memcpy(dest_addr, source_line, 1); + *dest_addr = *source_line; } } } diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 1a9399455..7994cb859 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -159,7 +159,7 @@ static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); return {raw, raw}; } else { const Tegra::Texture::TextureHandle handle{raw}; - return {handle.tic_id, via_header_index ? handle.tic_id : handle.tsc_id}; + return {handle.tic_id, handle.tsc_id}; } } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 44afdc1cd..8e56a89e1 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -526,6 +526,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR SetNext(next, workgroup_layout); } + VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR executable_properties; + if (khr_pipeline_executable_properties) { + LOG_INFO(Render_Vulkan, "Enabling shader feedback, expect slower shader build times"); + executable_properties = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR, + .pNext = nullptr, + .pipelineExecutableInfo = VK_TRUE, + }; + SetNext(next, executable_properties); + } + if (!ext_depth_range_unrestricted) { LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted"); } @@ -824,6 +835,7 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { bool has_khr_shader_float16_int8{}; bool has_khr_workgroup_memory_explicit_layout{}; + bool has_khr_pipeline_executable_properties{}; bool has_ext_subgroup_size_control{}; bool has_ext_transform_feedback{}; bool has_ext_custom_border_color{}; @@ -878,6 +890,10 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true); } + if (Settings::values.renderer_shader_feedback) { + test(has_khr_pipeline_executable_properties, + VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME, false); + } } VkPhysicalDeviceFeatures2KHR features{}; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; @@ -1033,6 +1049,19 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { khr_workgroup_memory_explicit_layout = true; } } + if (has_khr_pipeline_executable_properties) { + VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR executable_properties; + executable_properties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR; + executable_properties.pNext = nullptr; + features.pNext = &executable_properties; + physical.GetFeatures2KHR(features); + + if (executable_properties.pipelineExecutableInfo) { + extensions.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); + khr_pipeline_executable_properties = true; + } + } if (khr_push_descriptor) { VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor; push_descriptor.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index df394e384..c19f40746 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -214,6 +214,11 @@ public: return khr_push_descriptor; } + /// Returns true if VK_KHR_pipeline_executable_properties is enabled. + bool IsKhrPipelineEexecutablePropertiesEnabled() const { + return khr_pipeline_executable_properties; + } + /// Returns true if the device supports VK_KHR_workgroup_memory_explicit_layout. bool IsKhrWorkgroupMemoryExplicitLayoutSupported() const { return khr_workgroup_memory_explicit_layout; @@ -378,6 +383,7 @@ private: bool khr_spirv_1_4{}; ///< Support for VK_KHR_spirv_1_4. bool khr_workgroup_memory_explicit_layout{}; ///< Support for explicit workgroup layouts. bool khr_push_descriptor{}; ///< Support for VK_KHR_push_descritor. + bool khr_pipeline_executable_properties{}; ///< Support for executable properties. bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax. bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted. diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index aa173d19e..300a61205 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -228,7 +228,9 @@ void MemoryCommit::Release() { MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_) : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()}, - export_allocations{export_allocations_} {} + export_allocations{export_allocations_}, + buffer_image_granularity{ + device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} MemoryAllocator::~MemoryAllocator() = default; @@ -258,7 +260,9 @@ MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage } MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) { - auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), usage); + VkMemoryRequirements requirements = device.GetLogical().GetImageMemoryRequirements(*image); + requirements.size = Common::AlignUp(requirements.size, buffer_image_granularity); + auto commit = Commit(requirements, usage); image.BindMemory(commit.Memory(), commit.Offset()); return commit; } diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index b61e931e0..86e8ed119 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -123,6 +123,8 @@ private: const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties. const bool export_allocations; ///< True when memory allocations have to be exported. std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. + VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers + // and optimal images }; /// Returns true when a memory usage is guaranteed to be host visible. diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 70898004a..a9faa4807 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -181,6 +181,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetMemoryWin32HandleKHR); #endif X(vkGetQueryPoolResults); + X(vkGetPipelineExecutablePropertiesKHR); + X(vkGetPipelineExecutableStatisticsKHR); X(vkGetSemaphoreCounterValueKHR); X(vkMapMemory); X(vkQueueSubmit); @@ -809,6 +811,42 @@ VkMemoryRequirements Device::GetImageMemoryRequirements(VkImage image) const noe return requirements; } +std::vector<VkPipelineExecutablePropertiesKHR> Device::GetPipelineExecutablePropertiesKHR( + VkPipeline pipeline) const { + const VkPipelineInfoKHR info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, + .pNext = nullptr, + .pipeline = pipeline, + }; + u32 num{}; + dld->vkGetPipelineExecutablePropertiesKHR(handle, &info, &num, nullptr); + std::vector<VkPipelineExecutablePropertiesKHR> properties(num); + for (auto& property : properties) { + property.sType = VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR; + } + Check(dld->vkGetPipelineExecutablePropertiesKHR(handle, &info, &num, properties.data())); + return properties; +} + +std::vector<VkPipelineExecutableStatisticKHR> Device::GetPipelineExecutableStatisticsKHR( + VkPipeline pipeline, u32 executable_index) const { + const VkPipelineExecutableInfoKHR executable_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, + .pNext = nullptr, + .pipeline = pipeline, + .executableIndex = executable_index, + }; + u32 num{}; + dld->vkGetPipelineExecutableStatisticsKHR(handle, &executable_info, &num, nullptr); + std::vector<VkPipelineExecutableStatisticKHR> statistics(num); + for (auto& statistic : statistics) { + statistic.sType = VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR; + } + Check(dld->vkGetPipelineExecutableStatisticsKHR(handle, &executable_info, &num, + statistics.data())); + return statistics; +} + void Device::UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes, Span<VkCopyDescriptorSet> copies) const noexcept { dld->vkUpdateDescriptorSets(handle, writes.size(), writes.data(), copies.size(), copies.data()); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index d76bb4324..b7ae01c6c 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -295,6 +295,8 @@ struct DeviceDispatch : InstanceDispatch { #ifdef _WIN32 PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{}; #endif + PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR{}; + PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{}; PFN_vkGetQueryPoolResults vkGetQueryPoolResults{}; PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR{}; PFN_vkMapMemory vkMapMemory{}; @@ -879,6 +881,12 @@ public: VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept; + std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR( + VkPipeline pipeline) const; + + std::vector<VkPipelineExecutableStatisticKHR> GetPipelineExecutableStatisticsKHR( + VkPipeline pipeline, u32 executable_index) const; + void UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes, Span<VkCopyDescriptorSet> copies) const noexcept; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 7a5fcff20..19ba0dbba 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -102,9 +102,9 @@ add_executable(yuzu configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui - configuration/configure_service.cpp - configuration/configure_service.h - configuration/configure_service.ui + configuration/configure_network.cpp + configuration/configure_network.h + configuration/configure_network.ui configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index 848801cec..8fc0c5a36 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -438,7 +438,7 @@ void QtSoftwareKeyboardDialog::ShowInlineKeyboard( initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; initialize_parameters.enable_return_button = appear_parameters.enable_return_button; - initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + initialize_parameters.disable_cancel_button = appear_parameters.disable_cancel_button; SetKeyboardType(); SetControllerImage(); diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index b112dd7b0..652d99570 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -107,6 +107,7 @@ void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url, is_local = true; LoadExtractedFonts(); + FocusFirstLinkElement(); SetUserAgent(UserAgent::WebApplet); SetFinished(false); SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); @@ -121,6 +122,7 @@ void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url, const std::string& additional_args) { is_local = false; + FocusFirstLinkElement(); SetUserAgent(UserAgent::WebApplet); SetFinished(false); SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); @@ -208,7 +210,7 @@ void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { if (input_interpreter->IsButtonPressedOnce(button)) { page()->runJavaScript( QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)), - [&](const QVariant& variant) { + [this, button](const QVariant& variant) { if (variant.toBool()) { switch (button) { case HIDButton::A: @@ -364,6 +366,17 @@ void QtNXWebEngineView::LoadExtractedFonts() { Qt::QueuedConnection); } +void QtNXWebEngineView::FocusFirstLinkElement() { + QWebEngineScript focus_link_element; + + focus_link_element.setName(QStringLiteral("focus_link_element.js")); + focus_link_element.setSourceCode(QString::fromStdString(FOCUS_LINK_ELEMENT_SCRIPT)); + focus_link_element.setWorldId(QWebEngineScript::MainWorld); + focus_link_element.setInjectionPoint(QWebEngineScript::Deferred); + focus_link_element.setRunsOnSubFrames(true); + default_profile->scripts()->insert(focus_link_element); +} + #endif QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h index 7ad07409f..7e9f703fc 100644 --- a/src/yuzu/applets/qt_web_browser.h +++ b/src/yuzu/applets/qt_web_browser.h @@ -161,6 +161,9 @@ private: /// Loads the extracted fonts using JavaScript. void LoadExtractedFonts(); + /// Brings focus to the first available link element. + void FocusFirstLinkElement(); + InputCommon::InputSubsystem* input_subsystem; std::unique_ptr<UrlRequestInterceptor> url_interceptor; diff --git a/src/yuzu/applets/qt_web_browser_scripts.h b/src/yuzu/applets/qt_web_browser_scripts.h index 992837a85..c4ba8d40f 100644 --- a/src/yuzu/applets/qt_web_browser_scripts.h +++ b/src/yuzu/applets/qt_web_browser_scripts.h @@ -73,6 +73,12 @@ constexpr char LOAD_NX_FONT[] = R"( })(); )"; +constexpr char FOCUS_LINK_ELEMENT_SCRIPT[] = R"( +if (document.getElementsByTagName("a").length > 0) { + document.getElementsByTagName("a")[0].focus(); +} +)"; + constexpr char GAMEPAD_SCRIPT[] = R"( window.addEventListener("gamepadconnected", function(e) { console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 25b658b2a..2e0ade815 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -632,9 +632,9 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); renderer.RequestScreenshot( screenshot_image.bits(), - [=, this] { + [=, this](bool invert_y) { const std::string std_screenshot_path = screenshot_path.toStdString(); - if (screenshot_image.mirrored(false, true).save(screenshot_path)) { + if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); } else { LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 72027e773..377795326 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -692,6 +692,7 @@ void Config::ReadServiceValues() { qt_config->beginGroup(QStringLiteral("Services")); ReadBasicSetting(Settings::values.bcat_backend); ReadBasicSetting(Settings::values.bcat_boxcat_local); + ReadBasicSetting(Settings::values.network_interface); qt_config->endGroup(); } @@ -825,6 +826,7 @@ void Config::ReadRendererValues() { if (global) { ReadBasicSetting(Settings::values.fps_cap); ReadBasicSetting(Settings::values.renderer_debug); + ReadBasicSetting(Settings::values.renderer_shader_feedback); ReadBasicSetting(Settings::values.enable_nsight_aftermath); ReadBasicSetting(Settings::values.disable_shader_loop_safety_checks); } @@ -872,10 +874,6 @@ void Config::ReadShortcutValues() { void Config::ReadSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - ReadBasicSetting(Settings::values.current_user); - Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0, - Service::Account::MAX_USERS - 1); - ReadGlobalSetting(Settings::values.language_index); ReadGlobalSetting(Settings::values.region_index); @@ -896,6 +894,10 @@ void Config::ReadSystemValues() { } if (global) { + ReadBasicSetting(Settings::values.current_user); + Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0, + Service::Account::MAX_USERS - 1); + const auto custom_rtc_enabled = ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); if (custom_rtc_enabled) { @@ -945,7 +947,8 @@ void Config::ReadUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); ReadBasicSetting(UISettings::values.show_add_ons); - ReadBasicSetting(UISettings::values.icon_size); + ReadBasicSetting(UISettings::values.game_icon_size); + ReadBasicSetting(UISettings::values.folder_icon_size); ReadBasicSetting(UISettings::values.row_1_text_id); ReadBasicSetting(UISettings::values.row_2_text_id); ReadBasicSetting(UISettings::values.cache_game_list); @@ -1142,7 +1145,7 @@ void Config::SaveValues() { SaveDataStorageValues(); SaveDebuggingValues(); SaveDisabledAddOnValues(); - SaveServiceValues(); + SaveNetworkValues(); SaveUIValues(); SaveWebServiceValues(); SaveMiscellaneousValues(); @@ -1236,11 +1239,12 @@ void Config::SaveDebuggingValues() { qt_config->endGroup(); } -void Config::SaveServiceValues() { +void Config::SaveNetworkValues() { qt_config->beginGroup(QStringLiteral("Services")); WriteBasicSetting(Settings::values.bcat_backend); WriteBasicSetting(Settings::values.bcat_boxcat_local); + WriteBasicSetting(Settings::values.network_interface); qt_config->endGroup(); } @@ -1332,7 +1336,10 @@ void Config::SaveRendererValues() { static_cast<u32>(Settings::values.renderer_backend.GetDefault()), Settings::values.renderer_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.vulkan_device); - WriteGlobalSetting(Settings::values.fullscreen_mode); + WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()), + static_cast<u32>(Settings::values.fullscreen_mode.GetValue(global)), + static_cast<u32>(Settings::values.fullscreen_mode.GetDefault()), + Settings::values.fullscreen_mode.UsingGlobal()); WriteGlobalSetting(Settings::values.aspect_ratio); WriteGlobalSetting(Settings::values.max_anisotropy); WriteGlobalSetting(Settings::values.use_speed_limit); @@ -1360,6 +1367,7 @@ void Config::SaveRendererValues() { if (global) { WriteBasicSetting(Settings::values.fps_cap); WriteBasicSetting(Settings::values.renderer_debug); + WriteBasicSetting(Settings::values.renderer_shader_feedback); WriteBasicSetting(Settings::values.enable_nsight_aftermath); WriteBasicSetting(Settings::values.disable_shader_loop_safety_checks); } @@ -1400,7 +1408,6 @@ void Config::SaveShortcutValues() { void Config::SaveSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - WriteBasicSetting(Settings::values.current_user); WriteGlobalSetting(Settings::values.language_index); WriteGlobalSetting(Settings::values.region_index); WriteGlobalSetting(Settings::values.time_zone_index); @@ -1412,6 +1419,8 @@ void Config::SaveSystemValues() { 0, Settings::values.rng_seed.UsingGlobal()); if (global) { + WriteBasicSetting(Settings::values.current_user); + WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), false); WriteSetting(QStringLiteral("custom_rtc"), @@ -1458,7 +1467,8 @@ void Config::SaveUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); WriteBasicSetting(UISettings::values.show_add_ons); - WriteBasicSetting(UISettings::values.icon_size); + WriteBasicSetting(UISettings::values.game_icon_size); + WriteBasicSetting(UISettings::values.folder_icon_size); WriteBasicSetting(UISettings::values.row_1_text_id); WriteBasicSetting(UISettings::values.row_2_text_id); WriteBasicSetting(UISettings::values.cache_game_list); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 4bbb9f1cd..9555f4498 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -88,7 +88,7 @@ private: void SaveCoreValues(); void SaveDataStorageValues(); void SaveDebuggingValues(); - void SaveServiceValues(); + void SaveNetworkValues(); void SaveDisabledAddOnValues(); void SaveMiscellaneousValues(); void SavePathValues(); @@ -181,5 +181,6 @@ private: // These metatype declarations cannot be in common/settings.h because core is devoid of QT Q_DECLARE_METATYPE(Settings::CPUAccuracy); Q_DECLARE_METATYPE(Settings::GPUAccuracy); +Q_DECLARE_METATYPE(Settings::FullscreenMode); Q_DECLARE_METATYPE(Settings::RendererBackend); Q_DECLARE_METATYPE(Settings::ShaderBackend); diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp index 096e42e94..251aab912 100644 --- a/src/yuzu/configuration/configuration_shared.cpp +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -25,20 +25,6 @@ void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, } } -void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, - const QComboBox* combobox) { - if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { - setting->SetValue(combobox->currentIndex()); - } else if (!Settings::IsConfiguringGlobal()) { - if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - setting->SetGlobal(true); - } else { - setting->SetGlobal(false); - setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); - } - } -} - void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting) { if (setting->UsingGlobal()) { diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index 1e0ef01ca..5423dbc92 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -28,7 +28,20 @@ enum class CheckState { // ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, const CheckState& tracker); -void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); +template <typename Type> +void ApplyPerGameSetting(Settings::Setting<Type>* setting, const QComboBox* combobox) { + if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { + setting->SetValue(static_cast<Type>(combobox->currentIndex())); + } else if (!Settings::IsConfiguringGlobal()) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(static_cast<Type>(combobox->currentIndex() - + ConfigurationShared::USE_GLOBAL_OFFSET)); + } + } +} // Sets a Qt UI element given a Settings::Setting void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index fca9aed5f..6258dcf20 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -147,12 +147,12 @@ <string>Web</string> </attribute> </widget> - <widget class="ConfigureService" name="serviceTab"> + <widget class="ConfigureNetwork" name="networkTab"> <property name="accessibleName"> - <string>Services</string> + <string>Network</string> </property> <attribute name="title"> - <string>Services</string> + <string>Network</string> </attribute> </widget> </widget> @@ -242,9 +242,9 @@ <container>1</container> </customwidget> <customwidget> - <class>ConfigureService</class> + <class>ConfigureNetwork</class> <extends>QWidget</extends> - <header>configuration/configure_service.h</header> + <header>configuration/configure_network.h</header> <container>1</container> </customwidget> <customwidget> diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 8d7171487..784b6484e 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -65,6 +65,7 @@ void ConfigureCpu::UpdateGroup(int index) { } void ConfigureCpu::ApplyConfiguration() { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpu_accuracy, ui->accuracy); ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_unfuse_fma, ui->cpuopt_unsafe_unfuse_fma, cpuopt_unsafe_unfuse_fma); @@ -80,22 +81,6 @@ void ConfigureCpu::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check, ui->cpuopt_unsafe_fastmem_check, cpuopt_unsafe_fastmem_check); - - if (Settings::IsConfiguringGlobal()) { - // Guard if during game and set to game-specific value - if (Settings::values.cpu_accuracy.UsingGlobal()) { - Settings::values.cpu_accuracy.SetValue( - static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex())); - } - } else { - if (ui->accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - Settings::values.cpu_accuracy.SetGlobal(true); - } else { - Settings::values.cpu_accuracy.SetGlobal(false); - Settings::values.cpu_accuracy.SetValue(static_cast<Settings::CPUAccuracy>( - ui->accuracy->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); - } - } } void ConfigureCpu::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index f7e29dbd7..c0b240c1e 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -43,6 +43,8 @@ void ConfigureDebug::SetConfiguration() { ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); ui->enable_graphics_debugging->setEnabled(runtime_lock); ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); + ui->enable_shader_feedback->setEnabled(runtime_lock); + ui->enable_shader_feedback->setChecked(Settings::values.renderer_shader_feedback.GetValue()); ui->enable_cpu_debugging->setEnabled(runtime_lock); ui->enable_cpu_debugging->setChecked(Settings::values.cpu_debug_mode.GetValue()); ui->enable_nsight_aftermath->setEnabled(runtime_lock); @@ -65,6 +67,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); + Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); Settings::values.disable_shader_loop_safety_checks = diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index c8baf2921..3fe9ff7de 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -111,8 +111,8 @@ <property name="title"> <string>Graphics</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> - <item> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> <widget class="QCheckBox" name="enable_graphics_debugging"> <property name="enabled"> <bool>true</bool> @@ -125,7 +125,7 @@ </property> </widget> </item> - <item> + <item row="2" column="0"> <widget class="QCheckBox" name="enable_nsight_aftermath"> <property name="toolTip"> <string>When checked, it enables Nsight Aftermath crash dumps</string> @@ -135,7 +135,7 @@ </property> </widget> </item> - <item> + <item row="0" column="1"> <widget class="QCheckBox" name="disable_macro_jit"> <property name="enabled"> <bool>true</bool> @@ -148,7 +148,17 @@ </property> </widget> </item> - <item> + <item row="1" column="0"> + <widget class="QCheckBox" name="enable_shader_feedback"> + <property name="toolTip"> + <string>When checked, yuzu will log statistics about the compiled pipeline cache</string> + </property> + <property name="text"> + <string>Enable Shader Feedback</string> + </property> + </widget> + </item> + <item row="1" column="1"> <widget class="QCheckBox" name="disable_loop_safety_checks"> <property name="toolTip"> <string>When checked, it executes shaders without loop logic changes</string> @@ -276,11 +286,14 @@ <tabstop>open_log_button</tabstop> <tabstop>homebrew_args_edit</tabstop> <tabstop>enable_graphics_debugging</tabstop> + <tabstop>enable_shader_feedback</tabstop> <tabstop>enable_nsight_aftermath</tabstop> <tabstop>disable_macro_jit</tabstop> <tabstop>disable_loop_safety_checks</tabstop> + <tabstop>fs_access_log</tabstop> <tabstop>reporting_services</tabstop> <tabstop>quest_flag</tabstop> + <tabstop>enable_cpu_debugging</tabstop> <tabstop>use_debug_asserts</tabstop> <tabstop>use_auto_stub</tabstop> </tabstops> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index bc009b6b3..fe4186157 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -67,7 +67,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->audioTab->ApplyConfiguration(); ui->debugTab->ApplyConfiguration(); ui->webTab->ApplyConfiguration(); - ui->serviceTab->ApplyConfiguration(); + ui->networkTab->ApplyConfiguration(); Core::System::GetInstance().ApplySettings(); Settings::LogSettings(); } @@ -103,7 +103,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>); void ConfigureDialog::PopulateSelectionList() { const std::array<std::pair<QString, QList<QWidget*>>, 6> items{ {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}}, - {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, + {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->networkTab, ui->filesystemTab}}, {tr("CPU"), {ui->cpuTab}}, {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}}, {tr("Audio"), {ui->audioTab}}, diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 8ce97edec..69b6c2d66 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -25,6 +25,36 @@ <item> <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="fps_cap_label"> + <property name="text"> + <string>Framerate Cap</string> + </property> + <property name="toolTip"> + <string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="fps_cap"> + <property name="suffix"> + <string>x</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>1000</number> + </property> + <property name="value"> + <number>500</number> + </property> + </widget> + </item> + </layout> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QCheckBox" name="toggle_speed_limit"> @@ -52,36 +82,6 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="fps_cap_label"> - <property name="text"> - <string>Framerate Cap</string> - </property> - <property name="toolTip"> - <string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="fps_cap"> - <property name="suffix"> - <string>x</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>1000</number> - </property> - <property name="value"> - <number>500</number> - </property> - </widget> - </item> - </layout> - </item> - <item> <widget class="QCheckBox" name="use_multi_core"> <property name="text"> <string>Multicore CPU Emulation</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 1bc477c96..37e896258 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -98,7 +98,8 @@ void ConfigureGraphics::SetConfiguration() { if (Settings::IsConfiguringGlobal()) { ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); - ui->fullscreen_mode_combobox->setCurrentIndex(Settings::values.fullscreen_mode.GetValue()); + ui->fullscreen_mode_combobox->setCurrentIndex( + static_cast<int>(Settings::values.fullscreen_mode.GetValue())); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); } else { ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); @@ -310,8 +311,9 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, Settings::values.aspect_ratio.GetValue(true)); - ConfigurationShared::SetColoredComboBox(ui->fullscreen_mode_combobox, ui->fullscreen_mode_label, - Settings::values.fullscreen_mode.GetValue(true)); + ConfigurationShared::SetColoredComboBox( + ui->fullscreen_mode_combobox, ui->fullscreen_mode_label, + static_cast<int>(Settings::values.fullscreen_mode.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); } diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 38276feb1..a31b8e192 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -48,11 +48,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { } void ConfigureGraphicsAdvanced::ApplyConfiguration() { - // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) - const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( - ui->gpu_accuracy->currentIndex() - - ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); - + ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy); ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); @@ -63,20 +59,6 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { use_caches_gc); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); - - if (Settings::IsConfiguringGlobal()) { - // Must guard in case of a during-game configuration when set to be game-specific. - if (Settings::values.gpu_accuracy.UsingGlobal()) { - Settings::values.gpu_accuracy.SetValue(gpu_accuracy); - } - } else { - if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - Settings::values.gpu_accuracy.SetGlobal(true); - } else { - Settings::values.gpu_accuracy.SetGlobal(false); - Settings::values.gpu_accuracy.SetValue(gpu_accuracy); - } - } } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 379dc5d2e..4fe6b86ae 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -82,14 +82,17 @@ <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> </property> <property name="text"> - <string>Use asynchronous shader building</string> + <string>Use asynchronous shader building (hack)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="use_fast_gpu_time"> + <property name="toolTip"> + <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> + </property> <property name="text"> - <string>Use Fast GPU Time</string> + <string>Use Fast GPU Time (hack)</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 6b9bd05f1..7527c068b 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -309,11 +309,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i buttons_param[button_id].Clear(); button_map[button_id]->setText(tr("[not set]")); }); - context_menu.addAction(tr("Toggle button"), [&] { - const bool toggle_value = !buttons_param[button_id].Get("toggle", false); - buttons_param[button_id].Set("toggle", toggle_value); - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); + if (buttons_param[button_id].Has("toggle")) { + context_menu.addAction(tr("Toggle button"), [&] { + const bool toggle_value = + !buttons_param[button_id].Get("toggle", false); + buttons_param[button_id].Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + } if (buttons_param[button_id].Has("threshold")) { context_menu.addAction(tr("Set threshold"), [&] { const int button_threshold = static_cast<int>( diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index f50cda2f3..9c890ed5d 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -122,6 +122,7 @@ void PlayerControlPreview::UpdateColors() { colors.slider_arrow = QColor(14, 15, 18); colors.font2 = QColor(255, 255, 255); colors.indicator = QColor(170, 238, 255); + colors.indicator2 = QColor(100, 255, 100); colors.deadzone = QColor(204, 136, 136); colors.slider_button = colors.button; } @@ -139,6 +140,7 @@ void PlayerControlPreview::UpdateColors() { colors.slider_arrow = QColor(65, 68, 73); colors.font2 = QColor(0, 0, 0); colors.indicator = QColor(0, 0, 200); + colors.indicator2 = QColor(0, 150, 0); colors.deadzone = QColor(170, 0, 0); colors.slider_button = QColor(153, 149, 149); } @@ -317,8 +319,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) using namespace Settings::NativeAnalog; DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f, button_values[Settings::NativeButton::LStick]); - DrawRawJoystick(p, center + QPointF(-140, 90), axis_values[LStick].raw_value, - axis_values[LStick].properties); + DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0)); } using namespace Settings::NativeButton; @@ -432,8 +433,7 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center using namespace Settings::NativeAnalog; DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f, button_values[Settings::NativeButton::RStick]); - DrawRawJoystick(p, center + QPointF(140, 90), axis_values[RStick].raw_value, - axis_values[RStick].properties); + DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90)); } using namespace Settings::NativeButton; @@ -547,8 +547,7 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button); DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button); - DrawRawJoystick(p, center + QPointF(-180, 90), l_stick.raw_value, l_stick.properties); - DrawRawJoystick(p, center + QPointF(180, 90), r_stick.raw_value, r_stick.properties); + DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90)); } using namespace Settings::NativeButton; @@ -634,8 +633,7 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button); DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button); - DrawRawJoystick(p, center + QPointF(-50, 0), l_stick.raw_value, l_stick.properties); - DrawRawJoystick(p, center + QPointF(50, 0), r_stick.raw_value, r_stick.properties); + DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0)); } using namespace Settings::NativeButton; @@ -649,18 +647,18 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen // Face buttons p.setPen(colors.outline); button_color = colors.button; - DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius); - DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius); - DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius); - DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius); + DrawCircleButton(p, face_center + QPointF(face_distance, 0), button_values[A], face_radius); + DrawCircleButton(p, face_center + QPointF(0, face_distance), button_values[B], face_radius); + DrawCircleButton(p, face_center + QPointF(0, -face_distance), button_values[X], face_radius); + DrawCircleButton(p, face_center + QPointF(-face_distance, 0), button_values[Y], face_radius); // Face buttons text p.setPen(colors.transparent); p.setBrush(colors.font); - DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size); - DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size); - DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size); - DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size); + DrawSymbol(p, face_center + QPointF(face_distance, 0), Symbol::A, text_size); + DrawSymbol(p, face_center + QPointF(0, face_distance), Symbol::B, text_size); + DrawSymbol(p, face_center + QPointF(0, -face_distance), Symbol::X, text_size); + DrawSymbol(p, face_center + QPointF(-face_distance, 1), Symbol::Y, text_size); // D-pad constants const QPointF dpad_center = center + QPoint(-171, 8); @@ -671,18 +669,20 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen // D-pad buttons p.setPen(colors.outline); button_color = colors.button; - DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius); - DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius); - DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius); - DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius); + DrawCircleButton(p, dpad_center + QPointF(dpad_distance, 0), button_values[DRight], + dpad_radius); + DrawCircleButton(p, dpad_center + QPointF(0, dpad_distance), button_values[DDown], dpad_radius); + DrawCircleButton(p, dpad_center + QPointF(0, -dpad_distance), button_values[DUp], dpad_radius); + DrawCircleButton(p, dpad_center + QPointF(-dpad_distance, 0), button_values[DLeft], + dpad_radius); // D-pad arrows p.setPen(colors.font2); p.setBrush(colors.font2); - DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size); - DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size); - DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size); - DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size); + DrawArrow(p, dpad_center + QPointF(dpad_distance, 0), Direction::Right, dpad_arrow_size); + DrawArrow(p, dpad_center + QPointF(0, dpad_distance), Direction::Down, dpad_arrow_size); + DrawArrow(p, dpad_center + QPointF(0, -dpad_distance), Direction::Up, dpad_arrow_size); + DrawArrow(p, dpad_center + QPointF(-dpad_distance, 0), Direction::Left, dpad_arrow_size); // ZL and ZR buttons p.setPen(colors.outline); @@ -728,10 +728,7 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) button_values[Settings::NativeButton::LStick]); DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11, button_values[Settings::NativeButton::RStick]); - DrawRawJoystick(p, center + QPointF(-50, 105), axis_values[LStick].raw_value, - axis_values[LStick].properties); - DrawRawJoystick(p, center + QPointF(50, 105), axis_values[RStick].raw_value, - axis_values[RStick].properties); + DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105)); } using namespace Settings::NativeButton; @@ -821,10 +818,7 @@ void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) { p.setBrush(colors.font); DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C, 1.0f); - DrawRawJoystick(p, center + QPointF(-198, -125), axis_values[LStick].raw_value, - axis_values[LStick].properties); - DrawRawJoystick(p, center + QPointF(198, -125), axis_values[RStick].raw_value, - axis_values[RStick].properties); + DrawRawJoystick(p, center + QPointF(-198, -125), center + QPointF(198, -125)); } using namespace Settings::NativeButton; @@ -2358,8 +2352,33 @@ void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, boo DrawCircle(p, center, 7.5f); } -void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, const QPointF value, - const Input::AnalogProperties& properties) { +void PlayerControlPreview::DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right) { + using namespace Settings::NativeAnalog; + if (controller_type != Settings::ControllerType::LeftJoycon) { + DrawJoystickProperties(p, center_right, axis_values[RStick].properties); + p.setPen(colors.indicator); + p.setBrush(colors.indicator); + DrawJoystickDot(p, center_right, axis_values[RStick].raw_value, + axis_values[RStick].properties); + p.setPen(colors.indicator2); + p.setBrush(colors.indicator2); + DrawJoystickDot(p, center_right, axis_values[RStick].value, axis_values[RStick].properties); + } + + if (controller_type != Settings::ControllerType::RightJoycon) { + DrawJoystickProperties(p, center_left, axis_values[LStick].properties); + p.setPen(colors.indicator); + p.setBrush(colors.indicator); + DrawJoystickDot(p, center_left, axis_values[LStick].raw_value, + axis_values[LStick].properties); + p.setPen(colors.indicator2); + p.setBrush(colors.indicator2); + DrawJoystickDot(p, center_left, axis_values[LStick].value, axis_values[LStick].properties); + } +} + +void PlayerControlPreview::DrawJoystickProperties(QPainter& p, const QPointF center, + const Input::AnalogProperties& properties) { constexpr float size = 45.0f; const float range = size * properties.range; const float deadzone = size * properties.deadzone; @@ -2376,10 +2395,14 @@ void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, co pen.setColor(colors.deadzone); p.setPen(pen); DrawCircle(p, center, deadzone); +} + +void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, const QPointF value, + const Input::AnalogProperties& properties) { + constexpr float size = 45.0f; + const float range = size * properties.range; // Dot pointer - p.setPen(colors.indicator); - p.setBrush(colors.indicator); DrawCircle(p, center + (value * range), 2); } diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index 5fc16d8af..f4a6a5e1b 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -90,6 +90,7 @@ private: QColor highlight2{}; QColor transparent{}; QColor indicator{}; + QColor indicator2{}; QColor led_on{}; QColor led_off{}; QColor slider{}; @@ -139,7 +140,10 @@ private: // Draw joystick functions void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed); void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed); - void DrawRawJoystick(QPainter& p, QPointF center, QPointF value, + void DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right); + void DrawJoystickProperties(QPainter& p, QPointF center, + const Input::AnalogProperties& properties); + void DrawJoystickDot(QPainter& p, QPointF center, QPointF value, const Input::AnalogProperties& properties); void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed); void DrawGCJoystick(QPainter& p, QPointF center, bool pressed); diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_network.cpp index 4aa424803..ae22f1018 100644 --- a/src/yuzu/configuration/configure_service.cpp +++ b/src/yuzu/configuration/configure_network.cpp @@ -5,9 +5,11 @@ #include <QGraphicsItem> #include <QtConcurrent/QtConcurrent> #include "common/settings.h" +#include "core/core.h" #include "core/hle/service/bcat/backend/boxcat.h" -#include "ui_configure_service.h" -#include "yuzu/configuration/configure_service.h" +#include "core/network/network_interface.h" +#include "ui_configure_network.h" +#include "yuzu/configuration/configure_network.h" #ifdef YUZU_ENABLE_BOXCAT namespace { @@ -35,8 +37,8 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) { } // Anonymous namespace #endif -ConfigureService::ConfigureService(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) { +ConfigureNetwork::ConfigureNetwork(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()) { ui->setupUi(this); ui->bcat_source->addItem(QStringLiteral("None")); @@ -47,29 +49,42 @@ ConfigureService::ConfigureService(QWidget* parent) ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat")); #endif + ui->network_interface->addItem(tr("None")); + for (const auto& iface : Network::GetAvailableNetworkInterfaces()) { + ui->network_interface->addItem(QString::fromStdString(iface.name)); + } + connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - &ConfigureService::OnBCATImplChanged); + &ConfigureNetwork::OnBCATImplChanged); this->SetConfiguration(); } -ConfigureService::~ConfigureService() = default; +ConfigureNetwork::~ConfigureNetwork() = default; -void ConfigureService::ApplyConfiguration() { +void ConfigureNetwork::ApplyConfiguration() { Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString(); + Settings::values.network_interface = ui->network_interface->currentText().toStdString(); } -void ConfigureService::RetranslateUi() { +void ConfigureNetwork::RetranslateUi() { ui->retranslateUi(this); } -void ConfigureService::SetConfiguration() { +void ConfigureNetwork::SetConfiguration() { + const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + const int index = ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue())); ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index); + + const std::string& network_interface = Settings::values.network_interface.GetValue(); + + ui->network_interface->setCurrentText(QString::fromStdString(network_interface)); + ui->network_interface->setEnabled(runtime_lock); } -std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { +std::pair<QString, QString> ConfigureNetwork::BCATDownloadEvents() { #ifdef YUZU_ENABLE_BOXCAT std::optional<std::string> global; std::map<std::string, Service::BCAT::EventStatus> map; @@ -114,7 +129,7 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { #endif } -void ConfigureService::OnBCATImplChanged() { +void ConfigureNetwork::OnBCATImplChanged() { #ifdef YUZU_ENABLE_BOXCAT const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); ui->bcat_empty_header->setHidden(!boxcat); @@ -133,7 +148,7 @@ void ConfigureService::OnBCATImplChanged() { #endif } -void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) { +void ConfigureNetwork::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) { #ifdef YUZU_ENABLE_BOXCAT const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); if (boxcat) { diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_network.h index f5c1b703a..442b68e6b 100644 --- a/src/yuzu/configuration/configure_service.h +++ b/src/yuzu/configuration/configure_network.h @@ -9,15 +9,15 @@ #include <QWidget> namespace Ui { -class ConfigureService; +class ConfigureNetwork; } -class ConfigureService : public QWidget { +class ConfigureNetwork : public QWidget { Q_OBJECT public: - explicit ConfigureService(QWidget* parent = nullptr); - ~ConfigureService() override; + explicit ConfigureNetwork(QWidget* parent = nullptr); + ~ConfigureNetwork() override; void ApplyConfiguration(); void RetranslateUi(); @@ -29,6 +29,6 @@ private: void OnBCATImplChanged(); void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string); - std::unique_ptr<Ui::ConfigureService> ui; + std::unique_ptr<Ui::ConfigureNetwork> ui; QFutureWatcher<std::pair<QString, QString>> watcher{this}; }; diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_network.ui index 9668dd557..5f9b7e97b 100644 --- a/src/yuzu/configuration/configure_service.ui +++ b/src/yuzu/configuration/configure_network.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>ConfigureService</class> - <widget class="QWidget" name="ConfigureService"> + <class>ConfigureNetwork</class> + <widget class="QWidget" name="ConfigureNetwork"> <property name="geometry"> <rect> <x>0</x> @@ -17,21 +17,37 @@ <item> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>General</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="1"> + <widget class="QComboBox" name="network_interface"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Network Interface</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>BCAT</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="1" colspan="2"> - <widget class="QLabel" name="label_2"> - <property name="maximumSize"> - <size> - <width>260</width> - <height>16777215</height> - </size> - </property> + <item row="3" column="0"> + <widget class="QLabel" name="bcat_empty_header"> <property name="text"> - <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> </property> <property name="wordWrap"> <bool>true</bool> @@ -51,11 +67,8 @@ </property> </widget> </item> - <item row="3" column="1" colspan="2"> - <widget class="QLabel" name="bcat_empty_label"> - <property name="enabled"> - <bool>true</bool> - </property> + <item row="1" column="1" colspan="2"> + <widget class="QLabel" name="label_2"> <property name="maximumSize"> <size> <width>260</width> @@ -63,10 +76,7 @@ </size> </property> <property name="text"> - <string/> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string> </property> <property name="wordWrap"> <bool>true</bool> @@ -86,8 +96,17 @@ <item row="0" column="1" colspan="2"> <widget class="QComboBox" name="bcat_source"/> </item> - <item row="3" column="0"> - <widget class="QLabel" name="bcat_empty_header"> + <item row="3" column="1" colspan="2"> + <widget class="QLabel" name="bcat_empty_label"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="maximumSize"> + <size> + <width>260</width> + <height>16777215</height> + </size> + </property> <property name="text"> <string/> </property> diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 53b95658b..27f552f59 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -401,6 +401,11 @@ <string>Traditional Chinese (正體中文)</string> </property> </item> + <item> + <property name="text"> + <string>Brazilian Portuguese (português do Brasil)</string> + </property> + </item> </widget> </item> <item row="5" column="0"> diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index e8f41bf65..9d7d51126 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -16,7 +16,7 @@ #include "yuzu/uisettings.h" namespace { -constexpr std::array default_icon_sizes{ +constexpr std::array default_game_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), @@ -24,6 +24,13 @@ constexpr std::array default_icon_sizes{ std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), }; +constexpr std::array default_folder_icon_sizes{ + std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), + std::make_pair(24, QT_TRANSLATE_NOOP("ConfigureUI", "Small (24x24)")), + std::make_pair(48, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (48x48)")), + std::make_pair(72, QT_TRANSLATE_NOOP("ConfigureUI", "Large (72x72)")), +}; + // clang-format off constexpr std::array row_text_names{ QT_TRANSLATE_NOOP("ConfigureUI", "Filename"), @@ -34,8 +41,12 @@ constexpr std::array row_text_names{ }; // clang-format on -QString GetTranslatedIconSize(size_t index) { - return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second); +QString GetTranslatedGameIconSize(size_t index) { + return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second); +} + +QString GetTranslatedFolderIconSize(size_t index) { + return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second); } QString GetTranslatedRowTextName(size_t index) { @@ -60,8 +71,10 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur // Force game list reload if any of the relevant settings are changed. connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); - connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); + connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), + this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, @@ -95,7 +108,8 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); - UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); + UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); @@ -114,8 +128,10 @@ void ConfigureUi::SetConfiguration() { ui->language_combobox->setCurrentIndex( ui->language_combobox->findData(UISettings::values.language)); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); - ui->icon_size_combobox->setCurrentIndex( - ui->icon_size_combobox->findData(UISettings::values.icon_size.GetValue())); + ui->game_icon_size_combobox->setCurrentIndex( + ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); + ui->folder_icon_size_combobox->setCurrentIndex( + ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue())); ui->enable_screenshot_save_as->setChecked( UISettings::values.enable_screenshot_save_as.GetValue()); @@ -134,8 +150,14 @@ void ConfigureUi::changeEvent(QEvent* event) { void ConfigureUi::RetranslateUI() { ui->retranslateUi(this); - for (int i = 0; i < ui->icon_size_combobox->count(); i++) { - ui->icon_size_combobox->setItemText(i, GetTranslatedIconSize(static_cast<size_t>(i))); + for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) { + ui->game_icon_size_combobox->setItemText(i, + GetTranslatedGameIconSize(static_cast<size_t>(i))); + } + + for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) { + ui->folder_icon_size_combobox->setItemText( + i, GetTranslatedFolderIconSize(static_cast<size_t>(i))); } for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { @@ -166,9 +188,13 @@ void ConfigureUi::InitializeLanguageComboBox() { } void ConfigureUi::InitializeIconSizeComboBox() { - for (size_t i = 0; i < default_icon_sizes.size(); i++) { - const auto size = default_icon_sizes[i].first; - ui->icon_size_combobox->addItem(GetTranslatedIconSize(i), size); + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto size = default_game_icon_sizes[i].first; + ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size); + } + for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) { + const auto size = default_folder_icon_sizes[i].first; + ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size); } } diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index d895b799f..394f9fe04 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -81,16 +81,30 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> + <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> <item> - <widget class="QLabel" name="icon_size_label"> + <widget class="QLabel" name="game_icon_size_label"> <property name="text"> - <string>Icon Size:</string> + <string>Game Icon Size:</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="icon_size_combobox"/> + <widget class="QComboBox" name="game_icon_size_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="folder_icon_size_qhbox_layout_2"> + <item> + <widget class="QLabel" name="folder_icon_size_label"> + <property name="text"> + <string>Folder Icon Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="folder_icon_size_combobox"/> </item> </layout> </item> diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index 22ca1285d..f89ea8ea7 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -21,6 +21,7 @@ void ToggleConsole() { console_shown = UISettings::values.show_console.GetValue(); } + using namespace Common::Log; #if defined(_WIN32) && !defined(_DEBUG) FILE* temp; if (UISettings::values.show_console) { @@ -29,24 +30,20 @@ void ToggleConsole() { freopen_s(&temp, "CONIN$", "r", stdin); freopen_s(&temp, "CONOUT$", "w", stdout); freopen_s(&temp, "CONOUT$", "w", stderr); - Common::Log::AddBackend(std::make_unique<Common::Log::ColorConsoleBackend>()); + SetColorConsoleBackendEnabled(true); } } else { if (FreeConsole()) { // In order to close the console, we have to also detach the streams on it. // Just redirect them to NUL if there is no console window - Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name()); + SetColorConsoleBackendEnabled(false); freopen_s(&temp, "NUL", "r", stdin); freopen_s(&temp, "NUL", "w", stdout); freopen_s(&temp, "NUL", "w", stderr); } } #else - if (UISettings::values.show_console) { - Common::Log::AddBackend(std::make_unique<Common::Log::ColorConsoleBackend>()); - } else { - Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name()); - } + SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue()); #endif } } // namespace Debugger diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index f746bd85d..e97804220 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -244,8 +244,8 @@ void GameList::OnUpdateThemedIcons() { for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { QStandardItem* child = item_model->invisibleRootItem()->child(i); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { case GameListItemType::SdmcDir: child->setData( diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 982c0789d..9dc3cc7c3 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -80,7 +80,7 @@ public: setData(qulonglong(program_id), ProgramIdRole); setData(game_type, FileTypeRole); - const u32 size = UISettings::values.icon_size.GetValue(); + const u32 size = UISettings::values.game_icon_size.GetValue(); QPixmap picture; if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { @@ -233,8 +233,7 @@ public: UISettings::GameDir* game_dir = &directory; setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); switch (dir_type) { case GameListItemType::SdmcDir: setData( @@ -295,8 +294,8 @@ public: explicit GameListAddDir() { setData(type(), TypeRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + setData(QIcon::fromTheme(QStringLiteral("plus")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), @@ -318,8 +317,8 @@ public: explicit GameListFavorites() { setData(type(), TypeRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + setData(QIcon::fromTheme(QStringLiteral("star")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e172d2ff4..1bae1489f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -175,21 +175,6 @@ void GMainWindow::ShowTelemetryCallout() { const int GMainWindow::max_recent_files_item; -static void InitializeLogging() { - using namespace Common; - - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - - const auto log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); - void(FS::CreateDir(log_dir)); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); -#endif -} - static void RemoveCachedContents() { const auto cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir); const auto offline_fonts = cache_dir / "fonts"; @@ -207,8 +192,6 @@ GMainWindow::GMainWindow() : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, provider{std::make_unique<FileSys::ManualContentProvider>()} { - InitializeLogging(); - LoadTranslation(); setAcceptDrops(true); @@ -2513,7 +2496,7 @@ void GMainWindow::ShowFullscreen() { ui.menubar->hide(); statusBar()->hide(); - if (Settings::values.fullscreen_mode.GetValue() == 1) { + if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { showFullScreen(); return; } @@ -2528,7 +2511,7 @@ void GMainWindow::ShowFullscreen() { } else { UISettings::values.renderwindow_geometry = render_window->saveGeometry(); - if (Settings::values.fullscreen_mode.GetValue() == 1) { + if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { render_window->showFullScreen(); return; } @@ -2545,7 +2528,7 @@ void GMainWindow::ShowFullscreen() { void GMainWindow::HideFullscreen() { if (ui.action_Single_Window_Mode->isChecked()) { - if (Settings::values.fullscreen_mode.GetValue() == 1) { + if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { showNormal(); restoreGeometry(UISettings::values.geometry); } else { @@ -2559,7 +2542,7 @@ void GMainWindow::HideFullscreen() { statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); ui.menubar->show(); } else { - if (Settings::values.fullscreen_mode.GetValue() == 1) { + if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { render_window->showNormal(); render_window->restoreGeometry(UISettings::values.renderwindow_geometry); } else { @@ -2814,8 +2797,6 @@ void GMainWindow::OnToggleFilterBar() { } void GMainWindow::OnCaptureScreenshot() { - OnPauseGame(); - const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); const auto screenshot_path = QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)); @@ -2827,23 +2808,22 @@ void GMainWindow::OnCaptureScreenshot() { .arg(date); if (!Common::FS::CreateDir(screenshot_path.toStdString())) { - OnStartGame(); return; } #ifdef _WIN32 if (UISettings::values.enable_screenshot_save_as) { + OnPauseGame(); filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename, tr("PNG Image (*.png)")); + OnStartGame(); if (filename.isEmpty()) { - OnStartGame(); return; } } #endif render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(), filename); - OnStartGame(); } // TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant @@ -3401,6 +3381,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #endif int main(int argc, char* argv[]) { + Common::Log::Initialize(); Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -3440,6 +3421,7 @@ int main(int argc, char* argv[]) { // generating shaders setlocale(LC_ALL, "C"); + Core::System::InitializeGlobalInstance(); GMainWindow main_window; // After settings have been loaded by GMainWindow, apply the filter main_window.show(); diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 7b9d2dd53..81f741f20 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -91,7 +91,8 @@ struct Values { // Game List Settings::BasicSetting<bool> show_add_ons{true, "show_add_ons"}; - Settings::BasicSetting<uint32_t> icon_size{64, "icon_size"}; + Settings::BasicSetting<uint32_t> game_icon_size{64, "game_icon_size"}; + Settings::BasicSetting<uint32_t> folder_icon_size{48, "folder_icon_size"}; Settings::BasicSetting<uint8_t> row_1_text_id{3, "row_1_text_id"}; Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"}; std::atomic_bool is_game_list_reload_pending{false}; diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index e55a19649..74fc24972 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -1,5 +1,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) +# Credits to Samantas5855 and others for this function. function(create_resource file output filename) # Read hex data from file file(READ ${file} filedata HEX) diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 5af1ee6a8..4f14be524 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -278,6 +278,9 @@ void Config::ReadValues() { if (Settings::values.players.GetValue()[p].analogs[i].empty()) Settings::values.players.GetValue()[p].analogs[i] = default_param; } + + Settings::values.players.GetValue()[p].connected = + sdl2_config->GetBoolean(group, "connected", false); } ReadSetting("ControlsGeneral", Settings::values.mouse_enabled); @@ -444,6 +447,7 @@ void Config::ReadValues() { // Renderer ReadSetting("Renderer", Settings::values.renderer_backend); ReadSetting("Renderer", Settings::values.renderer_debug); + ReadSetting("Renderer", Settings::values.renderer_shader_feedback); ReadSetting("Renderer", Settings::values.enable_nsight_aftermath); ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks); ReadSetting("Renderer", Settings::values.vulkan_device); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index e646e2d2f..e02eceb99 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -221,6 +221,10 @@ backend = # 0 (default): Disabled, 1: Enabled debug = +# Enable shader feedback. +# 0 (default): Disabled, 1: Enabled +renderer_shader_feedback = + # Enable Nsight Aftermath crash dumps # 0 (default): Disabled, 1: Enabled nsight_aftermath = @@ -363,7 +367,7 @@ custom_rtc = # Sets the systems language index # 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, # 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, -# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese +# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese language_index = # The system region that yuzu will use during emulation diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 353e51ea7..87fce0c23 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -16,8 +16,8 @@ #include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/yuzu_icon.h" -EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_) - : input_subsystem{input_subsystem_} { +EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) + : input_subsystem{input_subsystem_}, system{system_} { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); @@ -122,9 +122,13 @@ void EmuWindow_SDL2::OnResize() { UpdateCurrentFramebufferLayout(width, height); } +void EmuWindow_SDL2::ShowCursor(bool show_cursor) { + SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE); +} + void EmuWindow_SDL2::Fullscreen() { switch (Settings::values.fullscreen_mode.GetValue()) { - case 1: // Exclusive fullscreen + case Settings::FullscreenMode::Exclusive: // Set window size to render size before entering fullscreen -- SDL does not resize to // display dimensions in this mode. // TODO: Multiply the window size by resolution_factor (for both docked modes) @@ -140,7 +144,7 @@ void EmuWindow_SDL2::Fullscreen() { LOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError()); LOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); [[fallthrough]]; - case 0: // Borderless window + case Settings::FullscreenMode::Borderless: if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { return; } @@ -218,7 +222,7 @@ void EmuWindow_SDL2::WaitEvent() { const u32 current_time = SDL_GetTicks(); if (current_time > last_time + 2000) { - const auto results = Core::System::GetInstance().GetAndResetPerfStats(); + const auto results = system.GetAndResetPerfStats(); const auto title = fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, @@ -228,6 +232,7 @@ void EmuWindow_SDL2::WaitEvent() { } } +// Credits to Samantas5855 and others for this function. void EmuWindow_SDL2::SetWindowIcon() { SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); if (yuzu_icon_stream == nullptr) { diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 1b9ab5b93..4810f8775 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -24,7 +24,7 @@ enum class MouseButton; class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem); + explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem, Core::System& system_); ~EmuWindow_SDL2(); /// Whether the window is still open, and a close request hasn't yet been sent @@ -67,6 +67,9 @@ protected: /// Called by WaitEvent when any event that may cause the window to be resized occurs void OnResize(); + /// Called when users want to hide the mouse cursor + void ShowCursor(bool show_cursor); + /// Called when user passes the fullscreen parameter flag void Fullscreen(); @@ -87,4 +90,7 @@ protected: /// Input subsystem to use with this window. InputCommon::InputSubsystem* input_subsystem; + + /// yuzu core instance + Core::System& system; }; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index eadb41790..a075ad08a 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -76,8 +76,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen) - : EmuWindow_SDL2{input_subsystem} { +EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, + Core::System& system_, bool fullscreen) + : EmuWindow_SDL2{input_subsystem, system_} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); @@ -110,6 +111,7 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste if (fullscreen) { Fullscreen(); + ShowCursor(false); } window_context = SDL_GL_CreateContext(render_window); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h index 9e694d985..d7f2c83d8 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h @@ -8,13 +8,18 @@ #include "core/frontend/emu_window.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" +namespace Core { +class System; +} + namespace InputCommon { class InputSubsystem; } class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen); + explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, Core::System& system_, + bool fullscreen); ~EmuWindow_SDL2_GL(); std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp index d1473dbab..de40b76bf 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -24,8 +24,9 @@ #include <SDL.h> #include <SDL_syswm.h> -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, bool fullscreen) - : EmuWindow_SDL2{input_subsystem} { +EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, + Core::System& system_, bool fullscreen) + : EmuWindow_SDL2{input_subsystem, system_} { const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); render_window = @@ -44,6 +45,7 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste if (fullscreen) { Fullscreen(); + ShowCursor(false); } switch (wm.subsystem) { diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h index de53844f0..3ea521b2a 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h @@ -19,7 +19,8 @@ class InputSubsystem; class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, bool fullscreen); + explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, Core::System& system, + bool fullscreen); ~EmuWindow_SDL2_VK() override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 35ce23696..ba2c993ba 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -74,31 +74,14 @@ static void PrintVersion() { std::cout << "yuzu " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; } -static void InitializeLogging() { - using namespace Common; - - Log::Filter log_filter(Log::Level::Debug); - log_filter.ParseFilterString(static_cast<std::string>(Settings::values.log_filter)); - Log::SetGlobalFilter(log_filter); - - Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); - - const auto& log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); - void(FS::CreateDir(log_dir)); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); -#endif -} - /// Application entry point int main(int argc, char** argv) { + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); Common::DetachedTasks detached_tasks; Config config; int option_index = 0; - - InitializeLogging(); #ifdef _WIN32 int argc_w; auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); @@ -163,6 +146,7 @@ int main(int argc, char** argv) { return -1; } + Core::System::InitializeGlobalInstance(); auto& system{Core::System::GetInstance()}; InputCommon::InputSubsystem input_subsystem; @@ -172,10 +156,10 @@ int main(int argc, char** argv) { std::unique_ptr<EmuWindow_SDL2> emu_window; switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: - emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, fullscreen); + emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, system, fullscreen); break; case Settings::RendererBackend::Vulkan: - emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem, fullscreen); + emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem, system, fullscreen); break; } |