diff --git a/.ci/license-header.sh b/.ci/license-header.sh index d49714dae1..565a4a6093 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -1,13 +1,13 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # specify full path if dupes may exist EXCLUDE_FILES="CPM.cmake CPMUtil.cmake GetSCMRev.cmake renderdoc_app.h tools/cpm tools/shellcheck.sh tools/update-cpm.sh tools/windows/vcvarsall.sh externals/stb externals/glad externals/getopt externals/gamemode externals/FidelityFX-FSR externals/demangle externals/bc_decoder externals/cmake-modules" # license header constants, please change when needed :)))) -YEAR=2025 +YEAR=2026 HOLDER="Eden Emulator Project" LICENSE="GPL-3.0-or-later" @@ -113,7 +113,7 @@ for file in $FILES; do [ "$excluded" = "true" ] && continue case "$file" in - *.cmake|*.sh|CMakeLists.txt) + *.cmake|*.sh|*CMakeLists.txt) begin="#" ;; *.kt*|*.cpp|*.h) @@ -186,7 +186,7 @@ if [ "$UPDATE" = "true" ]; then for file in $SRC_FILES $OTHER_FILES; do case $(basename -- "$file") in - *.cmake|CMakeLists.txt) + *.cmake|*CMakeLists.txt) begin="#" shell="false" ;; diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0630c85d..147d175989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later cmake_minimum_required(VERSION 3.22) @@ -13,13 +13,13 @@ set(CPM_SOURCE_CACHE ${CMAKE_SOURCE_DIR}/.cache/cpm) include(DetectPlatform) include(DetectArchitecture) include(DefaultConfig) -include(DownloadExternals) +include(UseLTO) +include(FasterLinker) +include(UseCcache) include(CMakeDependentOption) include(CTest) include(CPMUtil) -DetectArchitecture() - if (NOT DEFINED ARCHITECTURE) message(FATAL_ERROR "Architecture didn't make it out of scope, did you delete DetectArchitecture.cmake?") endif() @@ -41,13 +41,26 @@ if (PLATFORM_NETBSD) set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig") endif() +# qt stuff +option(ENABLE_QT "Enable the Qt frontend" ON) +option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) +option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF) +option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) +cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF) +set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") +cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) + +# non-linux bundled qt are static +if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX)) + set(YUZU_STATIC_BUILD ON) +endif() + +# TODO: does mingw need any of this anymore # static stuff option(YUZU_STATIC_BUILD "Use static libraries and executables if available" OFF) # TODO: StaticBuild.cmake if (YUZU_STATIC_BUILD) - include(StaticQtLibs) - # lol set(Boost_USE_STATIC_LIBS ON) set(BUILD_SHARED_LIBS OFF) @@ -121,15 +134,13 @@ if (CXX_CLANG_CL) $<$:-Wno-reserved-identifier> $<$:-Wno-deprecated-declarations> $<$:-Wno-cast-function-type-mismatch> - $<$:/EHsc> # thanks microsoft - ) + $<$:/EHsc>) # REQUIRED CPU features IN Windows-amd64 if (ARCHITECTURE_x86_64) add_compile_options( $<$:-msse4.1> - $<$:-mcx16> - ) + $<$:-mcx16>) endif() endif() @@ -146,21 +157,13 @@ cmake_dependent_option(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" O cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Build SDL2 from external source" OFF "ENABLE_SDL2;NOT MSVC" OFF) cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "ENABLE_SDL2" OFF) -# qt stuff -option(ENABLE_QT "Enable the Qt frontend" ON) -option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) -option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF) -cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) -option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) -option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) -set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") - option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) set(EXT_DEFAULT OFF) if (MSVC OR ANDROID) set(EXT_DEFAULT ON) endif() + option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT}) # ffmpeg @@ -169,9 +172,10 @@ cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external sour # sirit set(BUNDLED_SIRIT_DEFAULT OFF) -if ((MSVC AND NOT (CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo") OR ANDROID)) +if (MSVC AND NOT (CMAKE_BUILD_TYPE MATCHES "Deb") OR ANDROID) set(BUNDLED_SIRIT_DEFAULT ON) endif() + option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT}) # FreeBSD 15+ has libusb, versions below should disable it @@ -187,35 +191,6 @@ cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") -option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) -if(YUZU_ENABLE_LTO) - include(UseLTO) -endif() - -option(USE_CCACHE "Use ccache for compilation" OFF) -set(CCACHE_PATH "ccache" CACHE STRING "Path to ccache binary") -if(USE_CCACHE) - find_program(CCACHE_BINARY ${CCACHE_PATH}) - if(CCACHE_BINARY) - message(STATUS "Found ccache at: ${CCACHE_BINARY}") - set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_BINARY}) - set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_BINARY}) - else() - message(FATAL_ERROR "USE_CCACHE enabled, but no executable found at: ${CCACHE_PATH}") - endif() - # Follow SCCache recommendations: - # - if(WIN32) - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") - elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") - endif() - endif() -endif() - option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF) @@ -230,8 +205,6 @@ cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database") -cmake_dependent_option(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "PLATFORM_LINUX" OFF) - cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib" ON "APPLE" OFF) option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF) @@ -252,15 +225,16 @@ if (ENABLE_WEB_SERVICE OR USE_DISCORD_PRESENCE) endif() option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) + if (ENABLE_OPENSSL) set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF) if (EXT_DEFAULT OR PLATFORM_SUN) set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON) endif() - option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL}) endif() -# TODO(crueter): CPM this +cmake_dependent_option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL} "ENABLE_OPENSSL" OFF) + if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) AddJsonPackage(vulkan-validation-layers) @@ -324,44 +298,51 @@ if ((ANDROID OR APPLE OR UNIX) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32 endif() # Build/optimization presets -if (PLATFORM_LINUX OR CXX_CLANG) +if (CXX_GCC OR CXX_CLANG) if (ARCHITECTURE_x86_64) # See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html # Generic supports Pentium Pro instruction set and above + # TODO: if a value is unknown, pass that as march and mtune set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, v3, zen2, zen4, native") + if (${YUZU_BUILD_PRESET} STREQUAL "generic") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -mtune=generic") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -mtune=generic") + set(march x86-64) + set(mtune generic) elseif (${YUZU_BUILD_PRESET} STREQUAL "v3") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64-v3 -mtune=generic") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64-v3 -mtune=generic") + set(march x86-64-v3) + set(mtune generic) elseif (${YUZU_BUILD_PRESET} STREQUAL "zen2") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=znver2 -mtune=znver2") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=znver2 -mtune=znver2") + set(march znver2) + set(mtune znver2) elseif (${YUZU_BUILD_PRESET} STREQUAL "zen4") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=znver4 -mtune=znver4") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=znver4 -mtune=znver4") - elseif (${YUZU_BUILD_PRESET} STREQUAL "native") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -mtune=native") + set(march znver4) + set(mtune znver4) endif() elseif(ARCHITECTURE_arm64) # See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native") + set(mtune generic) + if (${YUZU_BUILD_PRESET} STREQUAL "generic") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a -mtune=generic") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a -mtune=generic") + set(march armv8-a) elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv9-a -mtune=generic") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv9-a -mtune=generic") - elseif (${YUZU_BUILD_PRESET} STREQUAL "native") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -mtune=native") + set(march armv9-a) endif() endif() + + if ("${YUZU_BUILD_PRESET}" STREQUAL "native") + set(march native) + set(mtune native) + endif() + + if (DEFINED march AND DEFINED mtune) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${march} -mtune=${mtune}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=${march} -mtune=${mtune}") + endif() endif() # Other presets, e.g. steamdeck +# TODO(crueter): Just have every Linux/Windows use old sdl2 set(YUZU_SYSTEM_PROFILE "generic" CACHE STRING "CMake and Externals profile to use. One of: generic, steamdeck") # Configure C++ standard @@ -376,9 +357,14 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) # System imported libraries # ======================================================================= +# Prefer the -pthread flag on Linux. +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + # openssl funniness if (ENABLE_OPENSSL) if (YUZU_USE_BUNDLED_OPENSSL) + set(BUILD_SHARED_LIBS OFF) AddJsonPackage(openssl) if (OpenSSL_ADDED) add_compile_definitions(YUZU_BUNDLED_OPENSSL) @@ -541,10 +527,6 @@ elseif (WIN32) endif() elseif (PLATFORM_HAIKU) # Haiku is so special :) - # Some fucking genius decided to name an entire module "network" in 2019 - # this caused great disaster amongst the Haiku community who had came first with - # their "libnetwork.so"; since CMake doesn't do magic, we have to use an ABSOLUTE PATH - # to the library itself, otherwise it will think we are linking to... our network thing set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so) elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") set(PLATFORM_LIBRARIES rt) @@ -563,17 +545,17 @@ find_package(VulkanUtilityLibraries) find_package(SimpleIni) find_package(SPIRV-Tools) find_package(sirit) -find_package(gamemode) +find_package(gamemode) +find_package(mcl) + +if (ARCHITECTURE_riscv64) + find_package(biscuit) +endif() if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) find_package(xbyak) endif() -if (ENABLE_WEB_SERVICE OR ENABLE_QT_UPDATE_CHECKER) - # Workaround: httplib will kill itself if you attempt to do a find_package propagation - # find_package(httplib CONFIG) -endif() - if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER) find_package(cpp-jwt) endif() @@ -598,9 +580,17 @@ if (YUZU_TESTS OR DYNARMIC_TESTS) find_package(Catch2) endif() +# Qt stuff if (ENABLE_QT) if (YUZU_USE_BUNDLED_QT) - download_qt(6.8.3) + # Qt 6.8+ is broken on macOS (??) + if (APPLE) + AddQt(6.7.3) + else() + AddQt(6.9.3) + endif() + + set(YUZU_STATIC_BUILD ON) else() message(STATUS "Using system Qt") if (NOT Qt6_DIR) @@ -609,13 +599,13 @@ if (ENABLE_QT) list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}") endif() - find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent) + find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Concurrent) if (YUZU_USE_QT_MULTIMEDIA) find_package(Qt6 REQUIRED COMPONENTS Multimedia) endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + if (PLATFORM_LINUX OR PLATFORM_FREEBSD) # yes Qt, we get it set(QT_NO_PRIVATE_MODULE_WARNING ON) find_package(Qt6 REQUIRED COMPONENTS DBus OPTIONAL_COMPONENTS GuiPrivate) @@ -644,28 +634,24 @@ if (ENABLE_QT) message(STATUS "Using target Qt at ${QT_TARGET_PATH}") message(STATUS "Using host Qt at ${QT_HOST_PATH}") -endif() -function(set_yuzu_qt_components) + ## Components ## + # Best practice is to ask for all components at once, so they are from the same version - set(YUZU_QT_COMPONENTS2 Core Widgets Concurrent) + set(YUZU_QT_COMPONENTS Core Widgets Concurrent) if (PLATFORM_LINUX) - list(APPEND YUZU_QT_COMPONENTS2 DBus) + list(APPEND YUZU_QT_COMPONENTS DBus) endif() if (YUZU_USE_QT_MULTIMEDIA) - list(APPEND YUZU_QT_COMPONENTS2 Multimedia) + list(APPEND YUZU_QT_COMPONENTS Multimedia) endif() if (YUZU_USE_QT_WEB_ENGINE) - list(APPEND YUZU_QT_COMPONENTS2 WebEngineCore WebEngineWidgets) + list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets) endif() if (ENABLE_QT_TRANSLATION) - list(APPEND YUZU_QT_COMPONENTS2 LinguistTools) + list(APPEND YUZU_QT_COMPONENTS LinguistTools) endif() - set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE) -endfunction(set_yuzu_qt_components) -if(ENABLE_QT) - set_yuzu_qt_components() find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS}) set(QT_MAJOR_VERSION 6) # Qt6 sets cxx_std_17 and we need to undo that @@ -683,21 +669,18 @@ if (NOT (YUZU_USE_BUNDLED_FFMPEG OR YUZU_USE_EXTERNAL_FFMPEG)) endif() if (WIN32 AND YUZU_CRASH_DUMPS) - set(BREAKPAD_VER "breakpad-c89f9dd") - download_bundled_external("breakpad/" ${BREAKPAD_VER} "breakpad-win" BREAKPAD_PREFIX "c89f9dd") + message(STATUS "YUZU_CRASH_DUMPS is unimplemented on Windows. Check back later.") + # set(BREAKPAD_VER "breakpad-c89f9dd") + # download_bundled_external("breakpad/" ${BREAKPAD_VER} "breakpad-win" BREAKPAD_PREFIX "c89f9dd") - set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include") - set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib") + # set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include") + # set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib") - add_library(libbreakpad_client INTERFACE IMPORTED) - target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}") - target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}") + # add_library(libbreakpad_client INTERFACE IMPORTED) + # target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}") + # target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}") endif() -# Prefer the -pthread flag on Linux. -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - # Include source code # =================== @@ -725,16 +708,11 @@ if (MSVC AND CXX_CLANG) add_library(llvm-mingw-runtime STATIC IMPORTED) set_target_properties(llvm-mingw-runtime PROPERTIES - IMPORTED_LOCATION "${LIB_PATH}" - ) + IMPORTED_LOCATION "${LIB_PATH}") link_libraries(llvm-mingw-runtime) endif() -if (YUZU_USE_FASTER_LD) - include(FasterLinker) -endif() - # Set runtime library to MD/MDd for all configurations if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") diff --git a/CMakeModules/CPM.cmake b/CMakeModules/CPM.cmake index 2d97f5493c..5544d8eefe 100644 --- a/CMakeModules/CPM.cmake +++ b/CMakeModules/CPM.cmake @@ -1,8 +1,3 @@ -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later - -# This is a slightly modified version of CPM.cmake - # CPM.cmake - CMake's missing package manager # =========================================== # See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index 6f36871b92..834e07e665 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -1,5 +1,5 @@ -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE) @@ -23,8 +23,17 @@ set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json") if(EXISTS ${CPMUTIL_JSON_FILE}) file(READ ${CPMUTIL_JSON_FILE} CPMFILE_CONTENT) + if (NOT TARGET cpmfiles) + add_custom_target(cpmfiles) + endif() + + target_sources(cpmfiles PRIVATE ${CPMUTIL_JSON_FILE}) + set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS + "${CPMUTIL_JSON_FILE}") else() - message(WARNING "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} does not exist, AddJsonPackage will be a no-op") + message(DEBUG "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE}" + "does not exist, AddJsonPackage will be a no-op") endif() # Utility stuff @@ -68,10 +77,10 @@ function(AddJsonPackage) set(oneValueArgs NAME - # these are overrides that can be generated at runtime, so can be defined separately from the json + # these are overrides that can be generated at runtime, + # so can be defined separately from the json DOWNLOAD_ONLY - BUNDLED_PACKAGE - ) + BUNDLED_PACKAGE) set(multiValueArgs OPTIONS) @@ -86,7 +95,8 @@ function(AddJsonPackage) endif() if(NOT DEFINED CPMFILE_CONTENT) - cpm_utils_message(WARNING ${name} "No cpmfile, AddJsonPackage is a no-op") + cpm_utils_message(WARNING ${name} + "No cpmfile, AddJsonPackage is a no-op") return() endif() @@ -94,7 +104,8 @@ function(AddJsonPackage) cpm_utils_message(FATAL_ERROR "json package" "No name specified") endif() - string(JSON object ERROR_VARIABLE err GET "${CPMFILE_CONTENT}" "${JSON_NAME}") + string(JSON object ERROR_VARIABLE + err GET "${CPMFILE_CONTENT}" "${JSON_NAME}") if(err) cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile") @@ -112,7 +123,8 @@ function(AddJsonPackage) get_json_element("${object}" raw_disabled disabled_platforms "") if(raw_disabled) - array_to_list("${raw_disabled}" ${raw_disabled_LENGTH} disabled_platforms) + array_to_list("${raw_disabled}" + ${raw_disabled_LENGTH} disabled_platforms) else() set(disabled_platforms "") endif() @@ -124,8 +136,7 @@ function(AddJsonPackage) PACKAGE ${package} EXTENSION ${extension} MIN_VERSION ${min_version} - DISABLED_PLATFORMS ${disabled_platforms} - ) + DISABLED_PLATFORMS ${disabled_platforms}) # pass stuff to parent scope set(${package}_ADDED "${${package}_ADDED}" @@ -153,8 +164,10 @@ function(AddJsonPackage) get_json_element("${object}" raw_patches patches "") # okay here comes the fun part: REPLACEMENTS! - # first: tag gets %VERSION% replaced if applicable, with either git_version (preferred) or version - # second: artifact gets %VERSION% and %TAG% replaced accordingly (same rules for VERSION) + # first: tag gets %VERSION% replaced if applicable, + # with either git_version (preferred) or version + # second: artifact gets %VERSION% and %TAG% replaced + # accordingly (same rules for VERSION) if(git_version) set(version_replace ${git_version}) @@ -179,9 +192,11 @@ function(AddJsonPackage) foreach(IDX RANGE ${range}) string(JSON _patch GET "${raw_patches}" "${IDX}") - set(full_patch "${CMAKE_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}") + set(full_patch + "${PROJECT_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}") if(NOT EXISTS ${full_patch}) - cpm_utils_message(FATAL_ERROR ${JSON_NAME} "specifies patch ${full_patch} which does not exist") + cpm_utils_message(FATAL_ERROR ${JSON_NAME} + "specifies patch ${full_patch} which does not exist") endif() list(APPEND patches "${full_patch}") @@ -223,8 +238,7 @@ function(AddJsonPackage) GIT_HOST ${git_host} ARTIFACT ${artifact} - TAG ${tag} - ) + TAG ${tag}) # pass stuff to parent scope set(${package}_ADDED "${${package}_ADDED}" @@ -280,8 +294,7 @@ function(AddPackage) KEY BUNDLED_PACKAGE FORCE_BUNDLED_PACKAGE - FIND_PACKAGE_ARGUMENTS - ) + FIND_PACKAGE_ARGUMENTS) set(multiValueArgs OPTIONS PATCHES) @@ -292,8 +305,17 @@ function(AddPackage) cpm_utils_message(FATAL_ERROR "package" "No package name defined") endif() - option(${PKG_ARGS_NAME}_FORCE_SYSTEM "Force the system package for ${PKG_ARGS_NAME}") - option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}") + set(${PKG_ARGS_NAME}_CUSTOM_DIR "" CACHE STRING + "Path to a separately-downloaded copy of ${PKG_ARGS_NAME}") + option(${PKG_ARGS_NAME}_FORCE_SYSTEM + "Force the system package for ${PKG_ARGS_NAME}") + option(${PKG_ARGS_NAME}_FORCE_BUNDLED + "Force the bundled package for ${PKG_ARGS_NAME}") + + if (DEFINED ${PKG_ARGS_NAME}_CUSTOM_DIR AND + NOT ${PKG_ARGS_NAME}_CUSTOM_DIR STREQUAL "") + set(CPM_${PKG_ARGS_NAME}_SOURCE ${${PKG_ARGS_NAME}_CUSTOM_DIR}) + endif() if(NOT DEFINED PKG_ARGS_GIT_HOST) set(git_host github.com) @@ -333,17 +355,19 @@ function(AddPackage) set(PKG_BRANCH ${PKG_ARGS_BRANCH}) else() cpm_utils_message(WARNING ${PKG_ARGS_NAME} - "REPO defined but no TAG, SHA, BRANCH, or URL specified, defaulting to master") + "REPO defined but no TAG, SHA, BRANCH, or URL" + "specified, defaulting to master") set(PKG_BRANCH master) endif() set(pkg_url ${pkg_git_url}/archive/refs/heads/${PKG_BRANCH}.tar.gz) endif() else() - cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} "No URL or repository defined") + cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} + "No URL or repository defined") endif() - cpm_utils_message(STATUS ${PKG_ARGS_NAME} "Download URL is ${pkg_url}") + cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "Download URL is ${pkg_url}") if(NOT DEFINED PKG_ARGS_KEY) if(DEFINED PKG_ARGS_SHA) @@ -402,7 +426,8 @@ function(AddPackage) # because "technically" the hash is invalidated each week # but it works for now kjsdnfkjdnfjksdn string(TOLOWER ${PKG_ARGS_NAME} lowername) - if(NOT EXISTS ${outfile} AND NOT EXISTS ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key}) + if(NOT EXISTS ${outfile} AND NOT EXISTS + ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key}) file(DOWNLOAD ${hash_url} ${outfile}) endif() @@ -428,7 +453,7 @@ function(AddPackage) - CPMUTIL_FORCE_BUNDLED - BUNDLED_PACKAGE - default to allow local - ]] # + ]] if(PKG_ARGS_FORCE_BUNDLED_PACKAGE) set_precedence(OFF OFF) elseif(${PKG_ARGS_NAME}_FORCE_SYSTEM) @@ -439,7 +464,8 @@ function(AddPackage) set_precedence(ON ON) elseif(CPMUTIL_FORCE_BUNDLED) set_precedence(OFF OFF) - elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset") + elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND + NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset") if(PKG_ARGS_BUNDLED_PACKAGE) set(local OFF) else() @@ -453,8 +479,7 @@ function(AddPackage) if(DEFINED PKG_ARGS_VERSION) list(APPEND EXTRA_ARGS - VERSION ${PKG_ARGS_VERSION} - ) + VERSION ${PKG_ARGS_VERSION}) endif() CPMAddPackage( @@ -471,8 +496,7 @@ function(AddPackage) ${EXTRA_ARGS} - ${PKG_ARGS_UNPARSED_ARGUMENTS} - ) + ${PKG_ARGS_UNPARSED_ARGUMENTS}) set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${PKG_ARGS_NAME}) set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${pkg_git_url}) @@ -516,24 +540,6 @@ function(AddPackage) endfunction() -function(add_ci_package key) - set(ARTIFACT ${ARTIFACT_NAME}-${key}-${ARTIFACT_VERSION}.${ARTIFACT_EXT}) - - AddPackage( - NAME ${ARTIFACT_PACKAGE} - REPO ${ARTIFACT_REPO} - TAG v${ARTIFACT_VERSION} - GIT_VERSION ${ARTIFACT_VERSION} - ARTIFACT ${ARTIFACT} - - KEY ${key}-${ARTIFACT_VERSION} - HASH_SUFFIX sha512sum - FORCE_BUNDLED_PACKAGE ON - ) - - set(ARTIFACT_DIR ${${ARTIFACT_PACKAGE}_SOURCE_DIR} PARENT_SCOPE) -endfunction() - # TODO(crueter): we could do an AddMultiArchPackage, multiplatformpackage? # name is the artifact name, package is for find_package override function(AddCIPackage) @@ -543,12 +549,17 @@ function(AddCIPackage) REPO PACKAGE EXTENSION - MIN_VERSION - ) + MIN_VERSION) set(multiValueArgs DISABLED_PLATFORMS) - cmake_parse_arguments(PKG_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(optionArgs MODULE) + + cmake_parse_arguments(PKG_ARGS + "${optionArgs}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN}) if(NOT DEFINED PKG_ARGS_VERSION) message(FATAL_ERROR "[CPMUtil] VERSION is required") @@ -589,55 +600,74 @@ function(AddCIPackage) set(ARTIFACT_REPO ${PKG_ARGS_REPO}) set(ARTIFACT_PACKAGE ${PKG_ARGS_PACKAGE}) - if((MSVC AND ARCHITECTURE_x86_64) AND NOT "windows-amd64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(windows-amd64) + if(MSVC AND ARCHITECTURE_x86_64) + set(pkgname windows-amd64) + elseif(MSVC AND ARCHITECTURE_arm64) + set(pkgname windows-arm64) + elseif(MINGW AND ARCHITECTURE_x86_64) + set(pkgname mingw-amd64) + elseif(MINGW AND ARCHITECTURE_arm64) + set(pkgname mingw-arm64) + elseif(ANDROID AND ARCHITECTURE_x86_64) + set(pkgname android-x86_64) + elseif(ANDROID AND ARCHITECTURE_arm64) + set(pkgname android-aarch64) + elseif(PLATFORM_SUN) + set(pkgname solaris-amd64) + elseif(PLATFORM_FREEBSD) + set(pkgname freebsd-amd64) + elseif(PLATFORM_LINUX AND ARCHITECTURE_x86_64) + set(pkgname linux-amd64) + elseif(PLATFORM_LINUX AND ARCHITECTURE_arm64) + set(pkgname linux-aarch64) + elseif(APPLE) + set(pkgname macos-universal) endif() - if((MSVC AND ARCHITECTURE_arm64) AND NOT "windows-arm64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(windows-arm64) - endif() + if (DEFINED pkgname AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS) + set(ARTIFACT "${ARTIFACT_NAME}-${pkgname}-${ARTIFACT_VERSION}.${ARTIFACT_EXT}") - if((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(mingw-amd64) - endif() + AddPackage( + NAME ${ARTIFACT_PACKAGE} + REPO ${ARTIFACT_REPO} + TAG "v${ARTIFACT_VERSION}" + GIT_VERSION ${ARTIFACT_VERSION} + ARTIFACT ${ARTIFACT} - if((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(mingw-arm64) - endif() + KEY "${pkgname}-${ARTIFACT_VERSION}" + HASH_SUFFIX sha512sum + FORCE_BUNDLED_PACKAGE ON + DOWNLOAD_ONLY ${PKG_ARGS_MODULE}) - if((ANDROID AND ARCHITECTURE_x86_64) AND NOT "android-x86_64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(android-x86_64) - endif() - - if((ANDROID AND ARCHITECTURE_arm64) AND NOT "android-aarch64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(android-aarch64) - endif() - - if(PLATFORM_SUN AND NOT "solaris-amd64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(solaris-amd64) - endif() - - if(PLATFORM_FREEBSD AND NOT "freebsd-amd64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(freebsd-amd64) - endif() - - if((PLATFORM_LINUX AND ARCHITECTURE_x86_64) AND NOT "linux-amd64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(linux-amd64) - endif() - - if((PLATFORM_LINUX AND ARCHITECTURE_arm64) AND NOT "linux-aarch64" IN_LIST DISABLED_PLATFORMS) - add_ci_package(linux-aarch64) - endif() - - # TODO(crueter): macOS amd64/aarch64 split mayhaps - if(APPLE AND NOT "macos-universal" IN_LIST DISABLED_PLATFORMS) - add_ci_package(macos-universal) - endif() - - if(DEFINED ARTIFACT_DIR) set(${ARTIFACT_PACKAGE}_ADDED TRUE PARENT_SCOPE) - set(${ARTIFACT_PACKAGE}_SOURCE_DIR "${ARTIFACT_DIR}" PARENT_SCOPE) + set(${ARTIFACT_PACKAGE}_SOURCE_DIR + "${${ARTIFACT_PACKAGE}_SOURCE_DIR}" PARENT_SCOPE) + + if (PKG_ARGS_MODULE) + list(APPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}") + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) + endif() else() find_package(${ARTIFACT_PACKAGE} ${ARTIFACT_MIN_VERSION} REQUIRED) endif() endfunction() + +# Utility function for Qt +function(AddQt version) + if (NOT DEFINED version) + message(FATAL_ERROR "[CPMUtil] AddQt: version is required") + endif() + + AddCIPackage( + NAME Qt + PACKAGE Qt6 + VERSION ${version} + MIN_VERSION 6 + REPO crueter-ci/Qt + DISABLED_PLATFORMS + android-x86_64 android-aarch64 + freebsd-amd64 solaris-amd64 openbsd-amd64 + MODULE) + + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) +endfunction() diff --git a/CMakeModules/CopyYuzuQt6Deps.cmake b/CMakeModules/CopyYuzuQt6Deps.cmake deleted file mode 100644 index 5ea8f74233..0000000000 --- a/CMakeModules/CopyYuzuQt6Deps.cmake +++ /dev/null @@ -1,66 +0,0 @@ -# SPDX-FileCopyrightText: 2024 kleidis - -function(copy_yuzu_Qt6_deps target_dir) - include(WindowsCopyFiles) - if (MSVC) - set(DLL_DEST "$/") - set(Qt6_DLL_DIR "${Qt6_DIR}/../../../bin") - else() - set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/") - set(Qt6_DLL_DIR "${Qt6_DIR}/../../../lib/") - endif() - set(Qt6_PLATFORMS_DIR "${Qt6_DIR}/../../../plugins/platforms/") - set(Qt6_STYLES_DIR "${Qt6_DIR}/../../../plugins/styles/") - set(Qt6_IMAGEFORMATS_DIR "${Qt6_DIR}/../../../plugins/imageformats/") - set(Qt6_RESOURCES_DIR "${Qt6_DIR}/../../../resources/") - set(PLATFORMS ${DLL_DEST}plugins/platforms/) - set(STYLES ${DLL_DEST}plugins/styles/) - set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/) - set(RESOURCES ${DLL_DEST}resources/) - if (MSVC) - windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} - Qt6Core$<$:d>.* - Qt6Gui$<$:d>.* - Qt6Widgets$<$:d>.* - Qt6Network$<$:d>.* - ) - if (YUZU_USE_QT_MULTIMEDIA) - windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} - Qt6Multimedia$<$:d>.* - ) - endif() - if (YUZU_USE_QT_WEB_ENGINE) - windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} - Qt6OpenGL$<$:d>.* - Qt6Positioning$<$:d>.* - Qt6PrintSupport$<$:d>.* - Qt6Qml$<$:d>.* - Qt6QmlMeta$<$:d>.* - Qt6QmlModels$<$:d>.* - Qt6QmlWorkerScript$<$:d>.* - Qt6Quick$<$:d>.* - Qt6QuickWidgets$<$:d>.* - Qt6WebChannel$<$:d>.* - Qt6WebEngineCore$<$:d>.* - Qt6WebEngineWidgets$<$:d>.* - QtWebEngineProcess$<$:d>.* - ) - windows_copy_files(${target_dir} ${Qt6_RESOURCES_DIR} ${RESOURCES} - icudtl.dat - qtwebengine_devtools_resources.pak - qtwebengine_resources.pak - qtwebengine_resources_100p.pak - qtwebengine_resources_200p.pak - v8_context_snapshot.bin - ) - endif() - windows_copy_files(yuzu ${Qt6_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$:d>.*) - windows_copy_files(yuzu ${Qt6_STYLES_DIR} ${STYLES} qmodernwindowsstyle$<$:d>.*) - windows_copy_files(yuzu ${Qt6_IMAGEFORMATS_DIR} ${IMAGEFORMATS} - qjpeg$<$:d>.* - qgif$<$:d>.* - ) - else() - # Update for non-MSVC platforms if needed - endif() -endfunction(copy_yuzu_Qt6_deps) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake deleted file mode 100644 index f6e3aaa4ad..0000000000 --- a/CMakeModules/DownloadExternals.cmake +++ /dev/null @@ -1,271 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -# This function downloads a binary library package from our external repo. -# Params: -# remote_path: path to the file to download, relative to the remote repository root -# prefix_var: name of a variable which will be set with the path to the extracted contents -set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) -function(download_bundled_external remote_path lib_name cpm_key prefix_var version) - set(package_base_url "https://github.com/eden-emulator/") - set(package_repo "no_platform") - set(package_extension "no_platform") - set(CACHE_KEY "") - - # TODO(crueter): Need to convert ffmpeg to a CI. - if (WIN32 OR FORCE_WIN_ARCHIVES) - if (ARCHITECTURE_arm64) - set(CACHE_KEY "windows") - set(package_repo "ext-windows-arm64-bin/raw/master/") - set(package_extension ".zip") - elseif(ARCHITECTURE_x86_64) - set(CACHE_KEY "windows") - set(package_repo "ext-windows-bin/raw/master/") - set(package_extension ".7z") - endif() - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - set(CACHE_KEY "linux") - set(package_repo "ext-linux-bin/raw/master/") - set(package_extension ".tar.xz") - elseif (ANDROID) - set(CACHE_KEY "android") - set(package_repo "ext-android-bin/raw/master/") - set(package_extension ".tar.xz") - else() - message(FATAL_ERROR "No package available for this platform") - endif() - string(CONCAT package_url "${package_base_url}" "${package_repo}") - string(CONCAT full_url "${package_url}" "${remote_path}" "${lib_name}" "${package_extension}") - message(STATUS "Resolved bundled URL: ${full_url}") - - # TODO(crueter): DELETE THIS ENTIRELY, GLORY BE TO THE CI! - AddPackage( - NAME ${cpm_key} - VERSION ${version} - URL ${full_url} - DOWNLOAD_ONLY YES - KEY ${CACHE_KEY} - BUNDLED_PACKAGE ON - # TODO(crueter): hash - ) - - if (DEFINED ${cpm_key}_SOURCE_DIR) - set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE) - message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}") - else() - message(FATAL_ERROR "AddPackage did not set ${cpm_key}_SOURCE_DIR") - endif() -endfunction() - -# Determine installation parameters for OS, architecture, and compiler -function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out) - if (WIN32) - set(host "windows") - set(type "desktop") - - if (NOT tool) - if (MINGW) - set(arch "win64_mingw") - set(arch_path "mingw_64") - elseif (MSVC) - if ("arm64" IN_LIST ARCHITECTURE) - set(arch_path "msvc2022_arm64") - elseif ("x86_64" IN_LIST ARCHITECTURE) - set(arch_path "msvc2022_64") - else() - message(FATAL_ERROR "Unsupported bundled Qt architecture. Disable YUZU_USE_BUNDLED_QT and provide your own.") - endif() - set(arch "win64_${arch_path}") - - if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") - set(host_arch_path "msvc2022_64") - elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") - set(host_arch_path "msvc2022_arm64") - endif() - set(host_arch "win64_${host_arch_path}") - else() - message(FATAL_ERROR "Unsupported bundled Qt toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.") - endif() - endif() - elseif (APPLE) - set(host "mac") - set(type "desktop") - set(arch "clang_64") - set(arch_path "macos") - else() - set(host "linux") - set(type "desktop") - set(arch "linux_gcc_64") - set(arch_path "gcc_64") - endif() - - set(${host_out} "${host}" PARENT_SCOPE) - set(${type_out} "${type}" PARENT_SCOPE) - set(${arch_out} "${arch}" PARENT_SCOPE) - set(${arch_path_out} "${arch_path}" PARENT_SCOPE) - if (DEFINED host_type) - set(${host_type_out} "${host_type}" PARENT_SCOPE) - else() - set(${host_type_out} "${type}" PARENT_SCOPE) - endif() - if (DEFINED host_arch) - set(${host_arch_out} "${host_arch}" PARENT_SCOPE) - else() - set(${host_arch_out} "${arch}" PARENT_SCOPE) - endif() - if (DEFINED host_arch_path) - set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE) - else() - set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE) - endif() -endfunction() - -# Download Qt binaries for a specific configuration. -function(download_qt_configuration prefix_out target host type arch arch_path base_path) - if (target MATCHES "tools_.*") - set(tool ON) - else() - set(tool OFF) - endif() - - set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini") - if (tool) - set(prefix "${base_path}/Tools") - list(APPEND install_args install-tool --outputdir "${base_path}" "${host}" desktop "${target}") - else() - set(prefix "${base_path}/${target}/${arch_path}") - list(APPEND install_args install-qt --outputdir "${base_path}" "${host}" "${type}" "${target}" "${arch}" -m qt_base) - - if (YUZU_USE_QT_MULTIMEDIA) - list(APPEND install_args qtmultimedia) - endif() - - if (YUZU_USE_QT_WEB_ENGINE) - list(APPEND install_args qtpositioning qtwebchannel qtwebengine) - endif() - - if (NOT "${YUZU_QT_MIRROR}" STREQUAL "") - message(STATUS "Using Qt mirror ${YUZU_QT_MIRROR}") - list(APPEND install_args -b "${YUZU_QT_MIRROR}") - endif() - endif() - - message(STATUS "Install Args: ${install_args}") - - if (NOT EXISTS "${prefix}") - message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") - set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0") - if (WIN32) - set(aqt_path "${base_path}/aqt.exe") - if (NOT EXISTS "${aqt_path}") - file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt.exe" "${aqt_path}" SHOW_PROGRESS) - endif() - execute_process(COMMAND "${aqt_path}" ${install_args} - WORKING_DIRECTORY "${base_path}" - RESULT_VARIABLE aqt_res - OUTPUT_VARIABLE aqt_out - ERROR_VARIABLE aqt_err) - if (NOT aqt_res EQUAL 0) - message(FATAL_ERROR "aqt.exe failed: ${aqt_err}") - endif() - elseif (APPLE) - set(aqt_path "${base_path}/aqt-macos") - if (NOT EXISTS "${aqt_path}") - file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt-macos" "${aqt_path}" SHOW_PROGRESS) - endif() - execute_process(COMMAND chmod +x "${aqt_path}") - execute_process(COMMAND "${aqt_path}" ${install_args} - WORKING_DIRECTORY "${base_path}" - RESULT_VARIABLE aqt_res - ERROR_VARIABLE aqt_err) - if (NOT aqt_res EQUAL 0) - message(FATAL_ERROR "aqt-macos failed: ${aqt_err}") - endif() - else() - find_program(PYTHON3_EXECUTABLE python3) - if (NOT PYTHON3_EXECUTABLE) - message(FATAL_ERROR "python3 is required to install Qt using aqt (pip mode).") - endif() - set(aqt_install_path "${base_path}/aqt") - file(MAKE_DIRECTORY "${aqt_install_path}") - - execute_process(COMMAND "${PYTHON3_EXECUTABLE}" -m pip install --target="${aqt_install_path}" aqtinstall - WORKING_DIRECTORY "${base_path}" - RESULT_VARIABLE pip_res - ERROR_VARIABLE pip_err) - if (NOT pip_res EQUAL 0) - message(FATAL_ERROR "pip install aqtinstall failed: ${pip_err}") - endif() - - execute_process(COMMAND "${CMAKE_COMMAND}" -E env PYTHONPATH="${aqt_install_path}" "${PYTHON3_EXECUTABLE}" -m aqt ${install_args} - WORKING_DIRECTORY "${base_path}" - RESULT_VARIABLE aqt_res - ERROR_VARIABLE aqt_err) - if (NOT aqt_res EQUAL 0) - message(FATAL_ERROR "aqt (python) failed: ${aqt_err}") - endif() - endif() - - message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}") - endif() - - set(${prefix_out} "${prefix}" PARENT_SCOPE) -endfunction() - -# This function downloads Qt using aqt. -# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH. -# QT_TARGET_PATH is set to the Qt for the compile target platform. -# QT_HOST_PATH is set to a host-compatible Qt, for running tools. -# Params: -# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool. -function(download_qt target) - determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path) - - set(base_path "${CMAKE_BINARY_DIR}/externals/qt") - file(MAKE_DIRECTORY "${base_path}") - - download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}") - if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}") - download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}") - else() - set(host_prefix "${prefix}") - endif() - - set(QT_TARGET_PATH "${prefix}" CACHE STRING "") - set(QT_HOST_PATH "${host_prefix}" CACHE STRING "") - - list(APPEND CMAKE_PREFIX_PATH "${prefix}") - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) -endfunction() - -function(download_moltenvk version platform) - if(NOT version) - message(FATAL_ERROR "download_moltenvk: version argument is required") - endif() - if(NOT platform) - message(FATAL_ERROR "download_moltenvk: platform argument is required") - endif() - - set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK") - set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") - - if(NOT EXISTS "${MOLTENVK_DIR}") - if(NOT EXISTS "${MOLTENVK_TAR}") - file(DOWNLOAD "https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar" - "${MOLTENVK_TAR}" SHOW_PROGRESS) - endif() - - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals" - RESULT_VARIABLE tar_res - ERROR_VARIABLE tar_err - ) - if(NOT tar_res EQUAL 0) - message(FATAL_ERROR "Extracting MoltenVK failed: ${tar_err}") - endif() - endif() - list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}") - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) -endfunction() - diff --git a/CMakeModules/Findzstd.cmake b/CMakeModules/Findzstd.cmake index 2afdb56a8c..8e44b7a892 100644 --- a/CMakeModules/Findzstd.cmake +++ b/CMakeModules/Findzstd.cmake @@ -14,8 +14,7 @@ else() pkg_search_module(ZSTD QUIET IMPORTED_TARGET libzstd) find_package_handle_standard_args(zstd REQUIRED_VARS ZSTD_LINK_LIBRARIES - VERSION_VAR ZSTD_VERSION - ) + VERSION_VAR ZSTD_VERSION) endif() if (zstd_FOUND AND NOT TARGET zstd::zstd) @@ -36,4 +35,7 @@ if (NOT TARGET zstd::libzstd) else() add_library(zstd::libzstd ALIAS zstd::zstd) endif() +elseif(YUZU_STATIC_BUILD AND TARGET zstd::libzstd_static) + # zstd::libzstd links to shared zstd by default + set_target_properties(zstd::libzstd PROPERTIES INTERFACE_LINK_LIBRARIES zstd::libzstd_static) endif() diff --git a/CMakeModules/StaticQtLibs.cmake b/CMakeModules/StaticQtLibs.cmake deleted file mode 100644 index be2f060a25..0000000000 --- a/CMakeModules/StaticQtLibs.cmake +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -## When linking to a static Qt build on MinGW, certain additional libraries -## must be statically linked to as well. - -function(static_qt_link target) - macro(extra_libs) - foreach(lib ${ARGN}) - find_library(${lib}_LIBRARY ${lib} REQUIRED) - target_link_libraries(${target} PRIVATE ${${lib}_LIBRARY}) - endforeach() - endmacro() - - # I am constantly impressed at how ridiculously stupid the linker is - # NB: yes, we have to put them here twice. I have no idea why - - # libtiff.a - extra_libs(tiff jbig bz2 lzma deflate jpeg tiff) - - # libfreetype.a - extra_libs(freetype bz2 Lerc brotlidec brotlicommon freetype) - - # libharfbuzz.a - extra_libs(harfbuzz graphite2) - - # sijfjkfnjkdfjsbjsbsdfhvbdf - if (ENABLE_OPENSSL) - target_link_libraries(${target} PRIVATE OpenSSL::SSL OpenSSL::Crypto) - endif() -endfunction() diff --git a/cpmfile.json b/cpmfile.json index 668ffec49c..b2ea69c36e 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -4,7 +4,7 @@ "package": "OpenSSL", "name": "openssl", "repo": "crueter-ci/OpenSSL", - "version": "3.6.0-965d6279e8", + "version": "3.6.0-1cb0d36b39", "min_version": "1.1.1" }, "boost": { diff --git a/dist/dev.eden_emu.eden.svg b/dist/dev.eden_emu.eden.svg index 5448c488e0..f88b52f625 100644 --- a/dist/dev.eden_emu.eden.svg +++ b/dist/dev.eden_emu.eden.svg @@ -6,141 +6,197 @@ viewBox="0 0 512 512" version="1.1" id="svg7" - sodipodi:docname="newyear2025.svg" - inkscape:version="1.4.2 (f4327f4, 2025-05-13)" - xml:space="preserve" + sodipodi:docname="base.svg.2026_01_12_14_43_47.0.svg" + inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - - - - New Year 2025 Logo - - - Madeline_Dev - mailto:madelvidel@gmail.com - - - 2025 - - 2025 Eden Emulator Project - https://git.eden-emu.dev - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:current-layer="svg7" /> + + + diff --git a/dist/eden.bmp b/dist/eden.bmp index 34d1ab616d..2f9b91c73b 100644 Binary files a/dist/eden.bmp and b/dist/eden.bmp differ diff --git a/dist/eden.icns b/dist/eden.icns index 9aad627054..680800951d 100644 Binary files a/dist/eden.icns and b/dist/eden.icns differ diff --git a/dist/eden.ico b/dist/eden.ico index fc579a29fe..5ce1a7095f 100644 Binary files a/dist/eden.ico and b/dist/eden.ico differ diff --git a/dist/eden_profile.jpg b/dist/eden_profile.jpg new file mode 100644 index 0000000000..5c458e5332 Binary files /dev/null and b/dist/eden_profile.jpg differ diff --git a/dist/icon_variations/2025.svg b/dist/icon_variations/2025.svg new file mode 100644 index 0000000000..96e7afc800 --- /dev/null +++ b/dist/icon_variations/2025.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/icon_variations/2025_named.svg b/dist/icon_variations/2025_named.svg new file mode 100644 index 0000000000..c374ceb63e --- /dev/null +++ b/dist/icon_variations/2025_named.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/icon_variations/base.svg b/dist/icon_variations/base.svg index 96e7afc800..f88b52f625 100644 --- a/dist/icon_variations/base.svg +++ b/dist/icon_variations/base.svg @@ -6,8 +6,11 @@ viewBox="0 0 512 512" version="1.1" id="svg7" - sodipodi:docname="base.svg" - inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + sodipodi:docname="base.svg.2026_01_12_14_43_47.0.svg" + inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" @@ -16,16 +19,96 @@ + id="stop3" /> + + + + + + + + + + + + + + + + + + + + + + + id="stop15" /> + + + + + + - - - - + style="stop-color:#bf42f6;stop-opacity:1;" + offset="0.44971901" + id="stop154" /> + + + + + + + + gradientTransform="translate(-6.9401139e-5,-2.8678628)" + x1="256.00012" + y1="102.94693" + x2="256.00012" + y2="409.05307" /> + + + - + gradientTransform="matrix(1.3229974,0,0,1.3214002,-82.687336,-82.290326)" /> - + id="path8-7" + style="display:inline;mix-blend-mode:multiply;fill:url(#linearGradient6);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2);stroke-width:3.9666;stroke-dasharray:none;stroke-opacity:0.566238;paint-order:stroke fill markers" + inkscape:label="Circle" + d="M 256,2.2792898 A 254.0155,253.71401 0 0 0 150.68475,25.115202 c 19.54414,1.070775 38.74692,5.250294 51.56848,11.647658 14.14361,7.056691 28.63804,19.185961 39.4212,29.347551 h 40.60981 c 1.03847,-0.68139 2.10297,-1.36938 3.1938,-2.05957 5.45602,-15.78533 14.79164,-43.183497 19.49612,-57.0097682 A 254.0155,253.71401 0 0 0 256,2.2792898 Z m 61.57106,7.567234 -18.26098,46.1544672 c 7.79702,-4.13918 16.35655,-7.87447 25.20671,-10.87081 23.1229,-7.828433 43.96931,-10.170904 54.94058,-10.868226 A 254.0155,253.71401 0 0 0 317.57106,9.8465238 Z m 65.39277,26.4001532 c -9.68256,4.806644 -33.05532,16.642034 -55.68217,29.863734 H 424.4677 A 254.0155,253.71401 0 0 0 382.96383,36.246677 Z M 113.90698,45.690231 A 254.0155,253.71401 0 0 0 87.532302,66.110411 H 194.2739 c -1.47402,-0.80231 -2.35141,-1.25949 -2.35141,-1.25949 l 10.4496,-11.83348 -38.40568,7.01234 c 0,1e-5 -12.21537,-4.60266 -40.17313,-12.27223 -3.45336,-0.94731 -6.75329,-1.61824 -9.8863,-2.06732 z m -36.803618,30.18635 a 254.0155,253.71401 0 0 0 -34.88372,43.090929 h 59.976738 c 18.11461,-12.04145 40.14252,-22.882149 62.31266,-24.534159 52.93006,-3.9444 70.16538,1.86342 70.16538,1.86342 0,0 -4.612,-4.8206 -14.51938,-13.36656 -2.72366,-2.34942 -6.0844,-4.77373 -9.52455,-7.05363 z m 174.472868,0 c 4.57322,4.7186 7.29716,7.83565 7.29716,7.83565 0,0 3.53501,-3.18484 9.62532,-7.83565 z m 60.27649,0 c -21.56573,15.45339 -25.4703,27.979669 -25.4703,27.979669 0,0 54.83326,-19.215729 100.70543,-0.31228 11.63986,4.79661 21.58481,10.13159 29.94832,15.42354 h 52.74419 A 254.0155,253.71401 0 0 0 434.89664,75.876581 Z M 36.250648,128.73367 A 254.0155,253.71401 0 0 0 16.372095,171.82459 H 147.45478 c 1.45695,-2.5815 3.06539,-5.08648 4.83979,-7.48982 14.23694,-19.28301 27.92088,-30.0088 36.86047,-35.6011 h -30.25323 c -5.87346,0.93472 -12.04945,1.99094 -18.28166,3.16937 -30.12936,5.69727 -81.157618,22.78945 -81.157618,22.78945 0,0 11.47125,-12.39249 29.11369,-25.95882 z m 265.630492,0 c 33.48676,11.2434 52.42799,26.78443 62.7752,43.09092 h 130.97157 a 254.0155,253.71401 0 0 0 -19.87856,-43.09092 h -44.81136 c 14.85233,11.5863 21.59948,20.9854 21.59948,20.9854 0,0 -33.5226,-12.37087 -66.0646,-20.9854 z m -45.96641,16.27007 c -1.00419,0.0106 -10.12705,0.72026 -44.98966,20.64729 -3.12132,1.78406 -6.25434,3.86182 -9.37468,6.17356 h 41.81911 c 7.17181,-17.34774 12.64083,-26.82085 12.64083,-26.82085 0,0 -0.0287,-7.1e-4 -0.0957,0 z m 14.18088,0.0465 c 0,0 -3.31228,9.32762 -7.30492,26.77438 h 51.78554 C 287.6577,146.14158 270.09561,145.0502 270.09561,145.0502 Z M 13.152456,181.59075 A 254.0155,253.71401 0 0 0 3.927651,224.68167 H 134.1447 c 0.56161,-12.72411 2.67825,-28.50188 8.61499,-43.09092 z m 176.661504,0 c -14.27121,13.10564 -27.60733,29.58761 -37.56073,43.09092 h 73.3721 c 4.47018,-16.79061 9.35068,-31.26371 13.86562,-43.09092 z m 70.85787,0 c -2.41384,11.76417 -4.9032,26.20707 -6.94831,43.09092 H 360.4832 c -8.32133,-10.88917 -20.66988,-26.17008 -36.35141,-43.09092 z m 109.17313,0 c 6.63611,15.24089 6.92441,30.5373 5.57882,43.09092 h 132.64857 a 254.0155,253.71401 0 0 0 -9.22481,-43.09092 z M 2.90181,234.44783 A 254.0155,253.71401 0 0 0 1.984498,255.9933 254.0155,253.71401 0 0 0 2.90181,277.53876 h 211.89923 c 2.25762,-15.52555 5.14325,-29.93448 8.3385,-43.09093 h -77.8863 c -6.46396,9.27617 -10.33076,15.56549 -10.33076,15.56549 0,0 -0.82623,-6.14945 -0.9354,-15.56549 z m 249.72093,0 c -1.3692,13.09684 -2.4456,27.49209 -3.02068,43.09093 h 259.49613 a 254.0155,253.71401 0 0 0 0.91731,-21.54546 254.0155,253.71401 0 0 0 -0.91731,-21.54547 H 374.02584 c -0.445,2.5469 -0.90878,4.89768 -1.32817,7.01751 0,0 -1.69726,-2.53821 -4.94056,-7.01751 z M 3.927651,287.30493 a 254.0155,253.71401 0 0 0 9.224805,43.09091 H 214.04393 c -1.29238,-15.40742 -1.57503,-30.04388 -0.41861,-43.09091 z m 245.385009,0 c -0.30355,13.54349 -0.22032,27.92598 0.36951,43.09091 h 249.16537 a 254.0155,253.71401 0 0 0 9.22481,-43.09091 z M 16.369511,340.16201 a 254.0155,253.71401 0 0 0 19.878554,43.09091 H 221.4677 c -2.69781,-14.4523 -4.96108,-29.01285 -6.4832,-43.09091 z m 233.842379,0 c 1.15864,15.47765 3.81286,29.83979 7.51679,43.09091 h 218.02325 a 254.0155,253.71401 0 0 0 19.87856,-43.09091 z M 42.217052,393.01909 a 254.0155,253.71401 0 0 0 34.88372,43.09093 H 233.09561 c -3.40902,-13.67281 -6.76794,-28.2531 -9.73902,-43.09093 z m 218.490958,0 c 5.34985,16.15926 12.22007,30.51982 19.68733,43.09093 h 154.50389 a 254.0155,253.71401 0 0 0 34.88371,-43.09093 z M 87.529722,445.87618 a 254.0155,253.71401 0 0 0 166.229968,63.8208 c -3.67805,-12.0825 -10.85464,-35.49828 -18.18088,-63.8208 z m 199.010328,0 c 17.5887,26.43772 36.99259,43.60598 47.33592,51.61309 a 254.0155,253.71401 0 0 0 90.59431,-51.61309 z" /> - - - - + id="path27" + style="display:inline;mix-blend-mode:multiply;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient27);stroke-width:3;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 318.98012,441.7375 c -9.87518,-6.73978 -64.39137,-49.0272 -67.68975,-127.81978 -3.69298,-88.21893 15.36468,-141.91029 15.36468,-141.91029 0,0 16.00378,0.99513 39.80316,26.53195 23.79939,25.53753 37.74965,46.43102 37.74965,46.43102 3.91262,-19.79992 12.84563,-66.32402 -60.72865,-87.55523 0,0 12.82326,-5.38883 39.3925,-3.81382 26.56907,1.57572 81.6822,21.93799 81.6822,21.93799 0,0 -14.79766,-20.63773 -49.47063,-34.94295 -34.67291,-14.30533 -76.1182,0.23644 -76.1182,0.23644 0,0 3.86959,-12.43127 27.22669,-26.38478 23.35718,-13.9537 49.27409,-26.501533 49.27409,-26.501533 0,0 -21.97854,-0.26548 -47.67725,8.44535 -6.68948,2.267506 -13.15863,5.094213 -19.05208,8.226563 l 16.05803,-40.634103 -4.4617,-1.89059 -5.1305,-0.95965 c 0,0 -11.24072,33.12428 -16.92051,49.576513 -12.13137,7.68489 -20.11005,14.87735 -20.11005,14.87735 0,0 -21.90573,-25.09227 -42.79668,-35.527803 -26.03412,-13.00525 -86.88249,-13.90359 -94.0044,10.401173 0,0 13.56804,-7.884703 34.70032,-2.080917 21.13214,5.803997 30.3644,9.287307 30.3644,9.287307 l 29.02989,-5.30681 -7.89811,8.95527 c 0,0 13.8496,7.21324 21.33822,13.68063 7.48859,6.46722 10.9757,10.11472 10.9757,10.11472 0,0 -13.02739,-4.39388 -53.03507,-1.40893 -40.00771,2.98473 -79.40016,45.60209 -79.40016,45.60209 0,0 38.57037,-12.93531 61.34393,-17.24677 22.77354,-4.31126 44.52166,-6.46757 44.52166,-6.46757 0,0 -17.23298,5.97003 -35.69792,31.00932 -18.46522,25.03987 -13.13146,64.83866 -13.13146,64.83866 0,0 29.33874,-47.7577 57.44675,-63.84249 28.10798,-16.08527 34.0799,-15.6238 34.0799,-15.6238 0,0 -22.56785,39.13486 -31.39017,101.98268 -8.03005,57.2039 26.77689,163.75449 31.1572,178.89699" + sodipodi:nodetypes="cscsccscscscsccccccscscccscscscscscsc" + inkscape:label="MainOutline" + clip-path="url(#clipPath128)" + transform="matrix(1.3229974,0,0,1.3214002,-82.687282,-82.278451)" /> diff --git a/dist/icon_variations/base_named.svg b/dist/icon_variations/base_named.svg index c374ceb63e..37f747d0fb 100644 --- a/dist/icon_variations/base_named.svg +++ b/dist/icon_variations/base_named.svg @@ -7,7 +7,10 @@ version="1.1" id="svg7" sodipodi:docname="base_named.svg" - inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" @@ -16,16 +19,96 @@ + id="stop3" /> + + + + + + + + + + + + + + + + + + + + + + + id="stop15" /> + + + + + + - - - - + style="stop-color:#bf42f6;stop-opacity:1;" + offset="0.44971901" + id="stop154" /> + + + + + + + + gradientTransform="translate(-6.9401139e-5,-2.8678628)" + x1="256.00012" + y1="102.94693" + x2="256.00012" + y2="409.05307" /> + + + + x1="256" + y1="64" + x2="256" + y2="448" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.3229974,0,0,1.3214002,-82.687336,-82.290326)" /> + id="swatch14" + inkscape:swatch="solid"> + + + x="-0.01907517" + y="-0.054959154" + width="1.0379885" + height="1.1092314"> + id="feComposite10" + k2="0" + k4="0" /> + + + - + id="path8-7" + style="display:inline;mix-blend-mode:multiply;fill:url(#linearGradient6);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2);stroke-width:3.9666;stroke-dasharray:none;stroke-opacity:0.566238;paint-order:stroke fill markers" + inkscape:label="Circle" + d="M 256,2.2792898 A 254.0155,253.71401 0 0 0 150.68475,25.115202 c 19.54414,1.070775 38.74692,5.250294 51.56848,11.647658 14.14361,7.056691 28.63804,19.185961 39.4212,29.347551 h 40.60981 c 1.03847,-0.68139 2.10297,-1.36938 3.1938,-2.05957 5.45602,-15.78533 14.79164,-43.183497 19.49612,-57.0097682 A 254.0155,253.71401 0 0 0 256,2.2792898 Z m 61.57106,7.567234 -18.26098,46.1544672 c 7.79702,-4.13918 16.35655,-7.87447 25.20671,-10.87081 23.1229,-7.828433 43.96931,-10.170904 54.94058,-10.868226 A 254.0155,253.71401 0 0 0 317.57106,9.8465238 Z m 65.39277,26.4001532 c -9.68256,4.806644 -33.05532,16.642034 -55.68217,29.863734 H 424.4677 A 254.0155,253.71401 0 0 0 382.96383,36.246677 Z M 113.90698,45.690231 A 254.0155,253.71401 0 0 0 87.532302,66.110411 H 194.2739 c -1.47402,-0.80231 -2.35141,-1.25949 -2.35141,-1.25949 l 10.4496,-11.83348 -38.40568,7.01234 c 0,1e-5 -12.21537,-4.60266 -40.17313,-12.27223 -3.45336,-0.94731 -6.75329,-1.61824 -9.8863,-2.06732 z m -36.803618,30.18635 a 254.0155,253.71401 0 0 0 -34.88372,43.090929 h 59.976738 c 18.11461,-12.04145 40.14252,-22.882149 62.31266,-24.534159 52.93006,-3.9444 70.16538,1.86342 70.16538,1.86342 0,0 -4.612,-4.8206 -14.51938,-13.36656 -2.72366,-2.34942 -6.0844,-4.77373 -9.52455,-7.05363 z m 174.472868,0 c 4.57322,4.7186 7.29716,7.83565 7.29716,7.83565 0,0 3.53501,-3.18484 9.62532,-7.83565 z m 60.27649,0 c -21.56573,15.45339 -25.4703,27.979669 -25.4703,27.979669 0,0 54.83326,-19.215729 100.70543,-0.31228 11.63986,4.79661 21.58481,10.13159 29.94832,15.42354 h 52.74419 A 254.0155,253.71401 0 0 0 434.89664,75.876581 Z M 36.250648,128.73367 A 254.0155,253.71401 0 0 0 16.372095,171.82459 H 147.45478 c 1.45695,-2.5815 3.06539,-5.08648 4.83979,-7.48982 14.23694,-19.28301 27.92088,-30.0088 36.86047,-35.6011 h -30.25323 c -5.87346,0.93472 -12.04945,1.99094 -18.28166,3.16937 -30.12936,5.69727 -81.157618,22.78945 -81.157618,22.78945 0,0 11.47125,-12.39249 29.11369,-25.95882 z m 265.630492,0 c 33.48676,11.2434 52.42799,26.78443 62.7752,43.09092 h 130.97157 a 254.0155,253.71401 0 0 0 -19.87856,-43.09092 h -44.81136 c 14.85233,11.5863 21.59948,20.9854 21.59948,20.9854 0,0 -33.5226,-12.37087 -66.0646,-20.9854 z m -45.96641,16.27007 c -1.00419,0.0106 -10.12705,0.72026 -44.98966,20.64729 -3.12132,1.78406 -6.25434,3.86182 -9.37468,6.17356 h 41.81911 c 7.17181,-17.34774 12.64083,-26.82085 12.64083,-26.82085 0,0 -0.0287,-7.1e-4 -0.0957,0 z m 14.18088,0.0465 c 0,0 -3.31228,9.32762 -7.30492,26.77438 h 51.78554 C 287.6577,146.14158 270.09561,145.0502 270.09561,145.0502 Z M 13.152456,181.59075 A 254.0155,253.71401 0 0 0 3.927651,224.68167 H 134.1447 c 0.56161,-12.72411 2.67825,-28.50188 8.61499,-43.09092 z m 176.661504,0 c -14.27121,13.10564 -27.60733,29.58761 -37.56073,43.09092 h 73.3721 c 4.47018,-16.79061 9.35068,-31.26371 13.86562,-43.09092 z m 70.85787,0 c -2.41384,11.76417 -4.9032,26.20707 -6.94831,43.09092 H 360.4832 c -8.32133,-10.88917 -20.66988,-26.17008 -36.35141,-43.09092 z m 109.17313,0 c 6.63611,15.24089 6.92441,30.5373 5.57882,43.09092 h 132.64857 a 254.0155,253.71401 0 0 0 -9.22481,-43.09092 z M 2.90181,234.44783 A 254.0155,253.71401 0 0 0 1.984498,255.9933 254.0155,253.71401 0 0 0 2.90181,277.53876 h 211.89923 c 2.25762,-15.52555 5.14325,-29.93448 8.3385,-43.09093 h -77.8863 c -6.46396,9.27617 -10.33076,15.56549 -10.33076,15.56549 0,0 -0.82623,-6.14945 -0.9354,-15.56549 z m 249.72093,0 c -1.3692,13.09684 -2.4456,27.49209 -3.02068,43.09093 h 259.49613 a 254.0155,253.71401 0 0 0 0.91731,-21.54546 254.0155,253.71401 0 0 0 -0.91731,-21.54547 H 374.02584 c -0.445,2.5469 -0.90878,4.89768 -1.32817,7.01751 0,0 -1.69726,-2.53821 -4.94056,-7.01751 z M 3.927651,287.30493 a 254.0155,253.71401 0 0 0 9.224805,43.09091 H 214.04393 c -1.29238,-15.40742 -1.57503,-30.04388 -0.41861,-43.09091 z m 245.385009,0 c -0.30355,13.54349 -0.22032,27.92598 0.36951,43.09091 h 249.16537 a 254.0155,253.71401 0 0 0 9.22481,-43.09091 z M 16.369511,340.16201 a 254.0155,253.71401 0 0 0 19.878554,43.09091 H 221.4677 c -2.69781,-14.4523 -4.96108,-29.01285 -6.4832,-43.09091 z m 233.842379,0 c 1.15864,15.47765 3.81286,29.83979 7.51679,43.09091 h 218.02325 a 254.0155,253.71401 0 0 0 19.87856,-43.09091 z M 42.217052,393.01909 a 254.0155,253.71401 0 0 0 34.88372,43.09093 H 233.09561 c -3.40902,-13.67281 -6.76794,-28.2531 -9.73902,-43.09093 z m 218.490958,0 c 5.34985,16.15926 12.22007,30.51982 19.68733,43.09093 h 154.50389 a 254.0155,253.71401 0 0 0 34.88371,-43.09093 z M 87.529722,445.87618 a 254.0155,253.71401 0 0 0 166.229968,63.8208 c -3.67805,-12.0825 -10.85464,-35.49828 -18.18088,-63.8208 z m 199.010328,0 c 17.5887,26.43772 36.99259,43.60598 47.33592,51.61309 a 254.0155,253.71401 0 0 0 90.59431,-51.61309 z" /> + id="path27" + style="display:inline;mix-blend-mode:multiply;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient27);stroke-width:3;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 318.98012,441.7375 c -9.87518,-6.73978 -64.39137,-49.0272 -67.68975,-127.81978 -3.69298,-88.21893 15.36468,-141.91029 15.36468,-141.91029 0,0 16.00378,0.99513 39.80316,26.53195 23.79939,25.53753 37.74965,46.43102 37.74965,46.43102 3.91262,-19.79992 12.84563,-66.32402 -60.72865,-87.55523 0,0 12.82326,-5.38883 39.3925,-3.81382 26.56907,1.57572 81.6822,21.93799 81.6822,21.93799 0,0 -14.79766,-20.63773 -49.47063,-34.94295 -34.67291,-14.30533 -76.1182,0.23644 -76.1182,0.23644 0,0 3.86959,-12.43127 27.22669,-26.38478 23.35718,-13.9537 49.27409,-26.501533 49.27409,-26.501533 0,0 -21.97854,-0.26548 -47.67725,8.44535 -6.68948,2.267506 -13.15863,5.094213 -19.05208,8.226563 l 16.05803,-40.634103 -4.4617,-1.89059 -5.1305,-0.95965 c 0,0 -11.24072,33.12428 -16.92051,49.576513 -12.13137,7.68489 -20.11005,14.87735 -20.11005,14.87735 0,0 -21.90573,-25.09227 -42.79668,-35.527803 -26.03412,-13.00525 -86.88249,-13.90359 -94.0044,10.401173 0,0 13.56804,-7.884703 34.70032,-2.080917 21.13214,5.803997 30.3644,9.287307 30.3644,9.287307 l 29.02989,-5.30681 -7.89811,8.95527 c 0,0 13.8496,7.21324 21.33822,13.68063 7.48859,6.46722 10.9757,10.11472 10.9757,10.11472 0,0 -13.02739,-4.39388 -53.03507,-1.40893 -40.00771,2.98473 -79.40016,45.60209 -79.40016,45.60209 0,0 38.57037,-12.93531 61.34393,-17.24677 22.77354,-4.31126 44.52166,-6.46757 44.52166,-6.46757 0,0 -17.23298,5.97003 -35.69792,31.00932 -18.46522,25.03987 -13.13146,64.83866 -13.13146,64.83866 0,0 29.33874,-47.7577 57.44675,-63.84249 28.10798,-16.08527 34.0799,-15.6238 34.0799,-15.6238 0,0 -22.56785,39.13486 -31.39017,101.98268 -8.03005,57.2039 26.77689,163.75449 31.1572,178.89699" + sodipodi:nodetypes="cscsccscscscsccccccscscccscscscscscsc" + inkscape:label="MainOutline" + clip-path="url(#clipPath128)" + transform="matrix(1.3229974,0,0,1.3214002,-82.687282,-82.278451)" /> - - - - + id="path2-1" + style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#bf42f6;stroke-width:13.374;stroke-dasharray:none;paint-order:stroke fill markers;filter:url(#filter7);stroke-opacity:1" + d="m 145.21909,346.52976 c -0.36717,0.0115 -0.78696,0.0621 -1.31244,0.14467 -0.89347,0.19595 -1.77894,0.37878 -2.69859,0.4744 -1.31703,0.0824 -2.63241,0.20309 -3.93581,0.38514 -1.3864,0.17422 -2.76512,0.37708 -4.14651,0.57743 -1.35068,0.15854 -2.66021,0.49126 -3.98033,0.77078 -1.69225,0.28843 -3.36884,0.61937 -5.0142,1.06303 -2.33483,0.621 -4.50209,1.60062 -6.66174,2.55931 -2.83865,1.16198 -5.52832,2.55991 -8.17138,4.01008 -3.18304,1.80707 -6.31423,3.67733 -9.462934,5.52666 -3.27462,1.87793 -6.52001,3.79152 -9.73875,5.73745 -2.92949,1.74613 -5.68427,3.68507 -8.45457,5.60525 -3.032465,2.15669 -6.061318,4.31477 -8.93096,6.6269 -2.438097,1.94649 -4.163062,4.26009 -5.395985,6.87905 -0.99348,2.24644 -1.524524,4.60499 -2.050822,6.95373 -0.617702,2.72368 -0.913067,5.48545 -1.09315,8.25123 -0.223191,2.30002 0.08835,4.5875 0.701535,6.82623 0.533463,1.87247 1.484494,3.59709 2.907462,5.09573 1.871362,1.59345 4.366617,2.73481 6.860779,3.46541 2.099408,0.49017 4.210321,0.94275 6.313131,1.42214 2.05676,0.5701 4.14641,0.92196 6.29716,1.09997 1.69101,0.11146 3.38661,0.14445 5.08207,0.15796 -0.15076,0.0732 -0.3014,0.14681 -0.45181,0.22041 -2.33784,1.23387 -4.69384,2.42557 -7.14796,3.48623 -2.96518,1.18663 -5.896997,2.43199 -8.823148,3.68609 -2.537803,1.07005 -4.969591,2.30177 -7.364192,3.58356 -2.46534,1.22533 -4.774267,2.63586 -6.956316,4.19588 -2.378638,1.72346 -4.531043,3.6552 -6.897588,5.38667 -2.288037,1.65131 -4.707011,3.1643 -7.017788,4.79152 -2.247338,1.62886 -4.199321,3.52352 -6.130999,5.41373 -2.61281,2.55699 -4.760433,5.41855 -6.903725,8.2624 -1.764012,2.36792 -3.309553,4.83915 -4.83082,7.32196 -1.194327,1.79711 -2.176048,3.67791 -3.105611,5.58079 -1.062396,2.13401 -1.832866,4.346 -2.172168,6.64355 -0.06136,1.42083 -0.103686,2.90892 0.371067,4.29138 0.240851,0.70133 0.582048,1.29129 0.930646,1.95637 1.902891,2.83603 4.153568,5.49001 6.407133,8.13048 1.288086,1.57628 2.808631,2.96283 4.499098,4.22788 1.656356,1.21431 3.617814,2.0486 5.736018,2.47188 1.796048,0.37107 3.631801,0.57806 5.47369,0.6883 2.272606,0.22605 4.523463,0.62308 6.757265,1.04116 2.501815,0.55125 5.036387,0.91279 7.611116,1.08331 2.084536,0.12762 4.175299,0.11465 6.263688,0.1119 2.31306,0.0138 4.620399,0.0129 6.928658,-0.1158 2.33201,-0.25186 4.673739,-0.4186 7.009736,-0.63964 2.29828,-0.15701 4.58784,-0.35708 6.85925,-0.6961 2.38907,-0.33242 4.76236,-0.74476 7.12553,-1.19001 4.085604,-0.77201 8.133414,-1.68394 12.177514,-2.59783 3.72474,-0.83576 7.37787,-1.87639 10.91882,-3.16148 3.87139,-1.48184 7.57887,-3.22884 11.24409,-5.04108 4.19745,-2.13428 8.1013,-4.65035 11.93271,-7.22334 2.5448,-1.73661 4.88729,-3.67828 7.1753,-5.65183 1.69158,-1.50672 3.42989,-2.97579 5.09652,-4.5024 1.52776,-1.39989 2.97206,-2.86497 4.37808,-4.35305 0.64813,-0.61944 1.01439,-1.24055 1.97834,-1.50021 0.84188,-0.14302 1.7091,-0.12189 2.56379,-0.12048 0.9621,0.0681 1.90164,-0.0742 2.85431,-0.16057 0.70283,-0.0807 1.41234,-0.0968 2.12026,-0.12385 0.10979,0.0446 0.22011,0.0883 0.33109,0.13089 0.26944,0.10328 0.572,0.12834 0.86094,0.18164 1.17005,0.21589 2.3027,0.30166 3.49413,0.22561 2.99483,-0.35833 5.81648,-1.28119 8.51077,-2.41047 3.32234,-1.3696 6.28682,-3.19511 9.16655,-5.13294 0.33712,-0.23713 0.67458,-0.47409 1.01204,-0.71093 0.68174,-0.34929 1.36159,-0.70142 2.03732,-1.05886 2.2878,-1.14164 4.34275,-2.56599 6.42248,-3.95387 2.40617,-1.41895 4.56604,-3.07126 6.5957,-4.85528 2.02202,-1.70896 3.81565,-3.58575 5.56246,-5.49544 1.74525,-2.07015 3.68258,-4.01022 5.61406,-5.95447 1.6565,-1.79596 3.27612,-3.61231 5.00805,-5.35751 2.27172,-2.25994 4.5814,-4.49297 6.96367,-6.66958 -0.0763,1.78184 -0.0713,3.56556 -0.0716,5.34842 -0.007,2.49667 -0.005,4.99339 -0.01,7.49006 0.0294,2.35513 0.41125,4.68986 0.84497,7.01176 0.40091,2.13971 1.16964,4.19673 2.16694,6.18504 1.00115,1.97028 2.26291,3.83646 3.66365,5.62009 1.1279,1.50221 2.54348,2.80298 4.10442,3.98248 1.86363,1.43702 3.24449,1.67392 5.47709,1.75497 2.04239,0.0309 4.08531,0.009 6.12792,0.005 1.75107,0.0538 3.47661,-0.20351 5.19602,-0.44317 1.60342,-0.30036 3.18505,-0.67257 4.7608,-1.0625 2.56727,-0.55555 4.89376,-1.61338 7.24716,-2.60901 2.32998,-1.09539 4.76246,-2.01876 7.14674,-3.02382 3.46012,-1.56318 6.93475,-3.10203 10.41418,-4.63435 2.81675,-1.22841 5.64171,-2.44659 8.40697,-3.75713 2.39728,-1.16597 4.85958,-2.22249 7.32793,-3.27363 1.03831,-0.36738 2.01998,-0.88439 3.0893,-1.18481 0.46217,-0.0607 0.92526,-0.11631 1.38923,-0.16785 -0.56044,2.59175 -0.63821,5.23049 -0.73132,7.8575 0.005,1.58917 -0.20287,3.18917 0.10382,4.76447 0.37284,1.57161 1.34209,2.94354 2.38346,4.25598 0.91695,1.01033 1.85561,2.01644 3.07211,2.77634 1.77404,1.03734 3.72625,1.71167 5.72739,2.33656 1.12718,0.29155 2.1884,0.67538 3.22814,1.14214 0.88665,0.41804 1.78433,0.72255 2.78307,0.87124 0.89852,0.11918 1.80746,0.12223 2.71427,0.12619 1.16922,-0.004 2.33843,-0.006 3.50764,-0.01 1.77463,0.0233 3.54278,-0.0177 5.31305,-0.11919 2.42095,-0.16067 4.79459,-0.55762 7.14058,-1.07473 2.23177,-0.52594 4.35297,-1.30595 6.48882,-2.05214 1.73617,-0.60367 3.42344,-1.29532 5.11373,-1.98395 2.64985,-1.19316 5.41369,-2.16981 8.23955,-3.01653 2.61309,-0.7402 5.20304,-1.53511 7.82491,-2.25303 l -1.8564,-1.06875 c -2.61749,0.73529 -5.21702,1.51472 -7.83259,2.25486 -2.83272,0.86233 -5.60376,1.85572 -8.2878,3.01315 -1.69028,0.6793 -3.37848,1.36189 -5.10325,1.97719 -2.12571,0.74021 -4.2323,1.5292 -6.4581,2.03756 -2.34169,0.50808 -4.71338,0.88925 -7.12832,1.03777 -1.77755,0.0935 -3.55338,0.11299 -5.33424,0.0911 -1.17291,0 -2.3458,0.006 -3.51869,0 -0.89802,-0.008 -1.79922,-0.0211 -2.68632,-0.15354 -0.97713,-0.17312 -1.86093,-0.51174 -2.737,-0.91443 -1.04427,-0.46165 -2.13064,-0.80818 -3.24748,-1.12287 -0.78081,-0.2449 -1.56449,-0.49597 -2.32696,-0.78068 -0.96523,-0.676 -1.75842,-1.50148 -2.5392,-2.33967 -1.03842,-1.28331 -2.00752,-2.62915 -2.3976,-4.17064 -0.34373,-1.56709 -0.15544,-3.16852 -0.18092,-4.75405 0.0637,-2.7026 0.168,-5.41622 0.76267,-8.07974 0.0729,-0.2927 0.14927,-0.58436 0.23004,-0.87487 0.60328,-0.0551 1.20689,-0.10743 1.81003,-0.16005 3.3477,-0.34914 6.71376,-0.58073 10.03486,-1.08721 3.00953,-0.46822 5.86473,-1.41652 8.61059,-2.53589 2.2372,-0.85163 4.11445,-2.19556 5.98694,-3.50889 2.24105,-1.55022 4.18469,-3.36264 6.11317,-5.1803 1.86482,-1.91304 3.55615,-3.93674 5.0354,-6.07704 1.17886,-1.6839 2.0508,-3.49049 2.72009,-5.35883 0.45963,-1.46079 0.77677,-2.96168 0.57806,-4.47326 -0.33866,-1.38948 -2.136,-2.05415 -3.6999,-2.16664 -0.87411,-0.0922 -1.69926,0 -2.54474,0.16915 -0.8179,0.0939 -1.63039,0.23256 -2.40743,0.47179 -1.01165,0.27796 -2.07885,0.31139 -3.11846,0.4632 -1.22962,0.15561 -2.37317,0.60604 -3.47202,1.08019 -1.7214,0.86438 -3.52247,1.61268 -5.22858,2.49895 -1.76435,0.9203 -3.38096,2.03037 -4.98779,3.13493 -1.86159,1.26002 -3.4597,2.75089 -5.01819,4.26873 -1.91794,1.84617 -3.61781,3.84458 -5.39352,5.78794 -1.78515,1.96392 -3.27903,4.10297 -4.653,6.2873 -1.08862,1.66086 -1.77799,3.43771 -2.2907,5.26721 -1.23053,0.0991 -2.46037,0.21141 -3.6735,0.40023 -1.05523,0.33572 -2.03549,0.83506 -3.0589,1.2327 -2.45093,1.07287 -4.90406,2.13944 -7.30276,3.29574 -2.76746,1.29321 -5.56662,2.53886 -8.37256,3.77067 -3.47811,1.54013 -6.95212,3.08693 -10.4369,4.61562 -2.40065,0.98149 -4.83216,1.91283 -7.1584,3.01939 -2.34798,0.99346 -4.68249,2.02401 -7.24717,2.56555 -1.58181,0.37009 -3.16341,0.74067 -4.76879,1.03154 -1.70506,0.24583 -3.41821,0.4748 -5.15517,0.42911 -2.04601,0.0104 -4.09299,0.0271 -6.13837,-0.0247 -0.52364,-0.0255 -0.96164,-0.0357 -1.47892,-0.10254 -0.13087,-0.0169 -0.26149,-0.036 -0.391,-0.0596 -1.42117,-1.08656 -2.74553,-2.25201 -3.76379,-3.64369 -1.40718,-1.77106 -2.67775,-3.62296 -3.6775,-5.58625 -1.00998,-1.97484 -1.79474,-4.01935 -2.20777,-6.14938 -0.4466,-2.31104 -0.80458,-4.6376 -0.85603,-6.98237 -0.008,-2.49852 -0.0108,-4.99699 -0.005,-7.49553 0.0182,-2.15667 0.0334,-4.31422 0.15388,-6.46893 0.038,-0.57566 0.0855,-1.15053 0.13761,-1.72504 1.76469,-1.54524 3.5072,-3.10826 5.31889,-4.61456 1.71074,-1.47376 3.39249,-2.97139 5.09344,-4.45325 1.97438,-1.70361 3.9423,-3.41242 5.86714,-5.15636 1.41127,-1.30276 2.85907,-2.57603 4.31575,-3.84222 2.45137,-2.20393 4.79423,-4.49369 7.23243,-6.70992 2.03392,-1.78423 4.04675,-3.5822 5.92151,-5.48815 1.64948,-1.54835 3.3683,-3.04212 5.06489,-4.5534 1.76046,-1.77675 3.62679,-3.47312 5.4399,-5.21051 1.14558,-1.25499 2.34171,-2.47372 3.54726,-3.68816 1.29977,-1.28385 2.52477,-2.62365 3.67073,-4.00904 0.92147,-1.21654 1.92048,-2.39024 2.79842,-3.63013 0.70701,-0.91919 1.23335,-1.92204 1.78207,-2.91219 0.6774,-1.16906 1.35582,-2.33474 1.70929,-3.60697 0.30518,-1.11851 0.0828,-2.23086 -0.41526,-3.27936 -0.47955,-0.82851 -1.17671,-1.54103 -1.84596,-2.26163 -1.18233,-1.31222 -3.10165,-1.96601 -4.868,-2.56738 -1.17233,-0.37085 -2.3725,-0.68122 -3.63264,-0.64926 -0.98969,0.0506 -1.9193,0.37377 -2.80213,0.73566 -0.9291,0.32435 -1.7398,0.83463 -2.5656,1.31049 -1.57669,0.89587 -3.14851,1.79834 -4.70981,2.71363 -2.97829,1.70286 -5.73561,3.66567 -8.39684,5.70569 -2.32752,1.84495 -4.76035,3.59006 -6.8792,5.61202 -2.0542,2.0224 -3.74055,4.2723 -5.39321,6.53478 -1.3056,2.15329 -2.64351,4.2912 -3.83843,6.4908 -1.14622,2.08342 -1.94875,4.27978 -2.88874,6.43147 -0.85959,2.03126 -1.72939,4.05916 -2.70936,6.05102 -1.06655,1.85289 -2.08295,3.72414 -3.03216,5.62294 -1.01071,2.05613 -1.74064,4.19888 -2.64332,6.2886 -0.60647,1.31019 -1.1994,2.6247 -1.7827,3.94242 -0.0934,-0.41552 -0.25171,-0.81719 -0.52369,-1.19313 -1.25957,-1.33146 -3.13464,-2.18485 -5.00835,-2.7511 -1.44021,-0.44037 -2.91955,-0.45955 -4.42387,-0.38357 -1.39732,0.0819 -2.79291,0.29497 -4.13021,0.64873 -1.16892,0.27443 -2.26821,0.68807 -3.36359,1.12158 -2.29249,0.8762 -4.45006,1.97495 -6.58496,3.09459 -2.71201,1.45232 -5.30045,3.05622 -7.87928,4.67027 -2.40545,1.54634 -4.96283,2.90912 -7.37617,4.44516 -3.23066,2.12678 -6.19992,4.51434 -9.24025,6.82831 -3.52574,2.61589 -7.048,5.23716 -10.34385,8.06204 -2.22791,2.01239 -4.15514,4.24197 -5.98262,6.5202 -1.04158,1.46235 -1.57173,3.11871 -2.08401,4.75588 -0.73909,2.24156 -0.85512,4.58106 -1.00652,6.89181 -0.17834,1.85342 0.005,3.68996 0.47761,5.49934 0.3684,0.84474 0.39768,1.98049 1.26822,2.61161 0.15812,0.11465 0.34379,0.1978 0.52184,0.2899 -0.49899,0.0235 -0.99735,0.0528 -1.49336,0.10721 -0.95072,0.0824 -1.89481,0.18719 -2.85309,0.13584 -0.87388,8e-5 -1.76605,-0.0199 -2.61722,0.172 -0.96807,0.32227 -1.37146,0.95257 -2.01795,1.61001 -1.37423,1.50079 -2.80252,2.9687 -4.33387,4.35696 -1.67455,1.51785 -3.40908,2.98761 -5.09068,4.50008 -2.25855,1.98086 -4.58703,3.91634 -7.10527,5.66094 -3.8009,2.58913 -7.6856,5.10629 -11.88972,7.21422 -3.67267,1.80394 -7.40378,3.51633 -11.27695,4.99554 -3.52673,1.27211 -7.16078,2.31354 -10.86138,3.16199 -4.05889,0.92041 -8.129544,1.81458 -12.241714,2.54917 -2.3696,0.41908 -4.73625,0.85743 -7.12768,1.17882 -2.27262,0.3216 -4.56055,0.51941 -6.85586,0.6844 -2.333959,0.22494 -4.674777,0.38891 -7.00667,0.63027 -2.303296,0.12131 -4.607413,0.12083 -6.914833,0.10851 -2.08551,-0.005 -4.17334,-0.004 -6.255088,-0.12543 -2.559027,-0.18066 -5.079341,-0.5507 -7.569064,-1.08983 -2.253474,-0.42629 -4.524726,-0.83664 -6.821696,-1.0474 -1.835483,-0.12214 -3.665641,-0.32451 -5.451311,-0.71587 -0.78951,-0.17779 -0.723815,-0.14446 -1.419025,-0.36563 -0.122726,-0.0757 -0.240489,-0.13978 -0.336595,-0.17903 -1.727826,-1.25857 -3.292318,-2.63587 -4.609101,-4.22008 -2.256001,-2.61522 -4.483847,-5.25708 -6.376361,-8.07609 -0.347244,-0.64851 -0.690788,-1.23401 -0.939038,-1.91656 -0.495689,-1.36344 -0.488018,-2.83476 -0.433333,-4.24375 0.332174,-2.28173 1.099304,-4.47336 2.163234,-6.58916 0.923517,-1.89923 1.910111,-3.77232 3.083503,-5.57246 1.508272,-2.48227 3.014649,-4.96532 4.765396,-7.33444 2.126326,-2.84361 4.309774,-5.67394 6.918524,-8.22209 1.942146,-1.87306 3.890338,-3.75896 6.144445,-5.37131 2.301843,-1.63227 4.690858,-3.17333 6.989542,-4.80922 2.385406,-1.71215 4.508032,-3.66718 6.88712,-5.389 2.20507,-1.5285 4.518229,-2.92699 6.972271,-4.15762 2.389376,-1.27411 4.804985,-2.51536 7.3387,-3.57576 2.926475,-1.24415 5.83499,-2.51923 8.79735,-3.70118 2.47599,-1.05831 4.86302,-2.23152 7.23303,-3.45059 1.25487,-0.60073 2.5045,-1.21657 3.83782,-1.68652 0.0507,-0.0177 0.10152,-0.0356 0.15235,-0.0536 0.29587,-0.0562 0.59125,-0.1155 0.88612,-0.17618 0.50499,-0.11514 0.97493,-0.30875 1.474004,-0.4377 0.94137,-0.24318 0.50496,-0.0861 -1.132154,-0.85457 -0.30527,-0.21205 -0.46016,-0.30297 -0.62412,-0.3102 -0.0367,-0.0192 -0.0739,-0.0387 -0.11149,-0.0588 -0.80946,0.16009 -1.57668,0.43427 -2.33986,0.70991 -1.33715,0.25202 -2.68853,0.43731 -4.06634,0.44289 -2.16057,-0.006 -4.32408,-0.0189 -6.47838,-0.1741 -2.1401,-0.19382 -4.216724,-0.57442 -6.271039,-1.12182 -2.108585,-0.4762 -4.222377,-0.93412 -6.319583,-1.44607 -0.292206,-0.0907 -0.589141,-0.17119 -0.8766,-0.27221 -0.293759,-0.10321 -1.062243,-0.542 -0.887051,-0.35337 -0.16939,-0.10726 -0.332525,-0.22171 -0.492972,-0.3383 -0.235906,-0.17143 -0.452328,-0.36118 -0.678488,-0.54179 -1.46058,-1.45914 -2.424245,-3.16452 -2.971045,-5.02286 -0.63918,-2.2204 -0.956039,-4.50045 -0.754976,-6.78771 0.167775,-2.75643 0.442267,-5.5126 1.094684,-8.22156 0.524999,-2.33794 1.037833,-4.6878 2.020421,-6.92668 1.207926,-2.6065 2.908094,-4.88402 5.330254,-6.81659 2.849591,-2.31963 5.861127,-4.48625 8.900545,-6.62482 2.78579,-1.90151 5.5447,-3.83252 8.46501,-5.58625 3.20621,-1.95352 6.43981,-3.87457 9.712644,-5.74734 3.16643,-1.82695 6.28879,-3.70745 9.47092,-5.51495 2.6644,-1.4267 5.3621,-2.81242 8.20578,-3.97156 2.15454,-0.95615 4.32502,-1.9181 6.66174,-2.5182 1.6479,-0.43323 3.32996,-0.73717 5.01482,-1.04819 1.321,-0.27833 2.63783,-0.58144 3.98648,-0.75024 1.37621,-0.20791 2.754,-0.39883 4.13544,-0.58004 1.3096,-0.16842 2.62721,-0.28162 3.94717,-0.37159 0.92145,-0.10506 1.81089,-0.29876 2.71366,-0.47804 0.36418,-0.0549 0.73293,-0.11584 1.10419,-0.12856 0.0807,0.61072 -0.29136,1.37154 -0.4125,1.9007 -0.67363,1.68139 -1.53591,3.29747 -2.40037,4.91382 -1.01671,1.90174 -2.12084,3.76922 -3.13598,5.67162 -1.26904,2.41232 -2.56585,4.81213 -3.8931,7.202 -1.59185,2.8756 -3.27283,5.71863 -4.70061,8.65716 -1.06118,2.22923 -2.06043,4.47688 -3.11387,6.70834 -0.92702,1.88529 -1.84876,3.80722 -3.32395,5.4421 l 2.02502,1.00578 c 1.4155,-1.6839 2.31535,-3.62317 3.25946,-5.516 1.06757,-2.227 2.07414,-4.47438 3.1197,-6.70913 1.3875,-2.95011 3.07854,-5.78782 4.6576,-8.66551 1.33816,-2.38581 2.67506,-4.76902 3.94133,-7.183 1.01156,-1.9027 2.12653,-3.76404 3.15595,-5.65988 0.8835,-1.61663 1.73045,-3.24514 2.40898,-4.93206 0.24078,-0.8929 0.91161,-2.07629 0.0366,-2.85311 -1.60426,-0.85373 -2.23412,-1.17012 -3.33564,-1.13561 z m 136.41538,10.6164 c 1.23492,0.0179 2.41268,0.32261 3.55833,0.69948 0.14148,0.0505 0.28421,0.099 0.42599,0.14886 0.18572,0.14685 0.37,0.30296 0.47855,0.42806 0.66756,0.71339 1.36501,1.41212 1.87974,2.21609 0.52678,1.00342 0.77373,2.08252 0.4887,3.17214 -0.34236,1.26098 -0.99449,2.42107 -1.68226,3.57185 -0.55333,0.98146 -1.08364,1.97519 -1.77839,2.89293 -0.85922,1.24299 -1.8399,2.42104 -2.76128,3.63092 -1.1456,1.37839 -2.36901,2.71365 -3.6655,3.99186 -1.20881,1.21045 -2.40474,2.42691 -3.53865,3.68817 -1.78835,1.74885 -3.62963,3.45811 -5.39598,5.22324 -1.71296,1.50808 -3.47723,2.97583 -5.11218,4.54561 -1.84397,1.91732 -3.82924,3.7267 -5.8472,5.51313 -2.43083,2.22371 -4.80003,4.49817 -7.28926,6.67426 -1.46892,1.25844 -2.92347,2.52936 -4.31757,3.84796 -1.90375,1.75724 -3.86033,3.47273 -5.85271,5.15792 -1.71113,1.47225 -3.39526,2.96617 -5.09345,4.44907 -1.05187,0.88321 -2.08232,1.78374 -3.10556,2.68995 0.0199,-0.2159 0.039,-0.43195 0.0574,-0.64796 0.0419,-0.77452 0.10669,-0.71857 -0.59372,-1.02633 0.0629,-0.15553 0.12655,-0.31066 0.18981,-0.46555 1.02288,-2.39192 2.0784,-4.7718 3.18575,-7.1359 0.90636,-2.08452 1.63669,-4.22328 2.65223,-6.27376 0.94784,-1.89985 1.95273,-3.77563 3.03586,-5.62243 0.98099,-1.99557 1.82475,-4.03499 2.69492,-6.06689 0.93533,-2.14769 1.74871,-4.335 2.89056,-6.41402 1.18386,-2.19541 2.49137,-4.33829 3.80558,-6.47936 1.63826,-2.25712 3.32014,-4.49727 5.37234,-6.50667 2.11882,-2.00841 4.53916,-3.75271 6.83742,-5.60993 2.65975,-2.03973 5.42722,-3.98553 8.42724,-5.65991 1.57052,-0.91634 3.17,-1.79528 4.76356,-2.6824 0.81997,-0.46882 1.62591,-0.96632 2.54841,-1.28395 0.86785,-0.34013 1.77162,-0.64752 2.74129,-0.66644 z m 125.28985,52.93008 v 0 0 0 0 c -1.1e-4,0.005 0.016,0.0301 0.011,0.0401 -0.004,0.008 -0.008,0.0155 -0.0123,0.0232 -0.0151,-0.006 -0.0303,-0.0131 -0.0455,-0.0195 -0.36244,0.41836 -0.68338,0.85882 -0.97919,1.31207 -1.21556,1.36275 -2.42315,2.73028 -3.63174,4.0975 -2.09696,2.36023 -4.0917,4.78526 -6.28947,7.08046 -1.28199,1.31908 -2.55466,2.6452 -3.86577,3.94372 -3.85962,3.82256 0.92854,-0.95802 -2.95845,2.87549 -0.44829,0.44211 -0.8861,0.89183 -1.32903,1.33781 -2.9276,3.15135 -5.70675,6.39908 -8.55746,9.60024 -2.54854,2.85123 -4.94662,5.79254 -7.11633,8.86275 -1.15886,1.6411 -2.29672,3.29401 -3.53897,4.89119 l 2.04131,0.98469 c 1.0626,-1.68111 2.17827,-3.33909 3.30984,-4.98773 2.09792,-3.09615 4.51959,-6.01801 7.00145,-8.8984 2.79567,-3.23678 5.63919,-6.44367 8.60783,-9.56823 2.69756,-2.7336 5.49267,-5.39571 8.16186,-8.14947 0.1955,-0.20028 0.3895,-0.40158 0.58174,-0.60398 -0.0278,0.0371 -0.0558,0.074 -0.0836,0.11111 -2.22788,2.8751 -4.41277,5.77386 -6.70535,8.61268 -2.29748,2.84831 -4.54765,5.72407 -6.72041,8.64182 -2.10186,2.82972 -4.17312,5.67502 -6.29717,8.49296 -1.45224,1.89873 -2.65942,3.92233 -3.61053,6.03463 -0.52818,1.25044 -1.12744,2.47958 -1.4614,3.78029 -0.14266,0.67061 -0.29107,1.34994 -0.17108,2.02951 0.37251,1.17307 1.67728,1.4225 2.90009,2.01179 0.69486,0.16085 1.38232,0.0663 2.06864,-0.0681 0.17317,-0.055 0.35279,-0.0976 0.51971,-0.16498 0.32642,-0.13191 1.36915,-0.65352 1.62665,-0.78511 1.03581,-0.52931 2.05238,-1.08578 3.06994,-1.63967 2.69145,-1.35318 4.93546,-3.21201 7.19926,-5.01819 2.3698,-1.84551 4.50915,-3.87865 6.61076,-5.93912 1.44107,-1.38261 2.70526,-2.88363 4.00552,-4.3598 1.27576,-1.44838 1.29954,-1.46155 2.62336,-2.91245 2.5668,-2.80039 5.13493,-5.59987 7.8332,-8.31056 3.01064,-2.92148 6.44428,-5.5046 9.99216,-7.94519 1.52886,-1.03762 3.10645,-2.03043 4.81209,-2.85129 0.73184,-0.3522 1.40277,-0.63347 2.15188,-0.95763 2.03089,-0.87607 4.11889,-1.65321 6.20746,-2.42425 1.8223,-0.70948 3.7319,-1.22747 5.66384,-1.67169 1.87466,-0.4139 3.76612,-0.77007 5.66719,-1.08489 0.43673,-0.0501 0.90094,-0.15011 1.36004,-0.18658 0.0878,0.14936 0.1666,0.30253 0.24604,0.45644 0.41672,0.85299 0.56342,1.74559 0.56208,2.66184 -0.0724,1.44334 -0.57672,2.83053 -1.14967,4.17974 -0.75269,1.58869 -1.83233,3.04355 -2.90869,4.48524 -1.15529,1.44547 -2.38721,2.84568 -3.52853,4.29919 -1.34013,1.68268 -2.34449,3.52885 -3.22628,5.40956 -0.96585,2.13582 -1.59202,4.36329 -2.03393,6.61518 -0.2563,1.56547 -0.38972,3.14871 -0.37011,4.72934 0.0892,1.14554 0.22023,2.29594 0.58818,3.40323 0.32035,1.07651 1.03305,1.99306 1.73968,2.90905 0.76146,0.92396 1.41789,1.90711 2.229,2.80107 0.50021,0.54654 1.08869,1.01637 1.75748,1.40964 1.16142,-0.10809 1.66553,0.92188 2.5733,1.25689 0.47873,0.17666 1.01318,0.28113 1.51855,0.38617 0.754,0.13956 1.511,0.24056 2.28056,0.28885 1.01045,0.0465 2.02259,0.0403 3.03403,0.0348 2.07536,-0.0756 4.14076,-0.28487 6.19395,-0.54543 2.31453,-0.37577 4.63383,-0.757 6.91392,-1.26469 1.64137,-0.42504 3.20604,-1.03276 4.76846,-1.6285 2.17229,-0.91417 4.2049,-2.04793 6.21822,-3.18853 2.63332,-1.4877 4.76269,-3.47218 6.81809,-5.48582 0.73687,-0.71218 1.46622,-1.42986 2.20286,-2.14217 0.10733,-0.10377 0.21088,-0.20994 0.31822,-0.31174 0.30446,0.47453 0.58136,0.96073 0.78692,1.47313 0.39971,0.8656 0.34004,1.77408 0.29454,2.68396 -0.22287,1.93643 -0.62035,3.87348 -1.29401,5.73667 -0.14506,0.40119 -0.32369,0.79321 -0.4856,1.18976 -0.50069,0.96252 -0.89838,2.0494 -1.76335,2.83982 -0.28893,0.26405 -0.42235,0.30198 -0.77677,0.47804 -0.16608,0.0719 -0.53997,0.19561 -0.63242,0.37082 -0.34178,0.64759 0.47564,1.7107 0.8164,2.24392 0.60678,0.82357 0.29766,0.4307 0.92237,1.18116 l 2.06065,0.75596 c -0.64747,-0.73901 -0.33063,-0.35199 -0.94601,-1.164 -0.22847,-0.34946 -1.07683,-1.55149 -0.87537,-1.98472 0.0635,-0.13661 0.49292,-0.30483 0.58511,-0.34845 0.13908,-0.0776 0.2884,-0.14369 0.41742,-0.2329 1.1189,-0.77358 1.50084,-2.20719 2.05543,-3.26869 0.15929,-0.40198 0.33206,-0.80026 0.47793,-1.20588 0.67108,-1.86591 1.11192,-3.79017 1.43101,-5.71975 0.0711,-0.91726 0.20637,-1.8403 -0.19228,-2.72041 -0.2458,-0.67396 -0.63884,-1.2948 -1.03203,-1.91447 -0.61207,-0.92497 -1.57958,-2.27012 -3.12922,-1.94231 -0.38291,0.25007 -0.64727,0.56895 -0.94848,0.88945 -0.69262,0.73698 -1.40173,1.46205 -2.11902,2.18198 -2.01067,2.03066 -4.15848,3.97563 -6.79166,5.44053 -2.02177,1.13895 -4.08471,2.23826 -6.28609,3.11411 -1.54955,0.58762 -3.09684,1.1952 -4.72273,1.61834 -2.26374,0.51839 -4.56531,0.91563 -6.87367,1.26235 -2.06956,0.25459 -4.15281,0.45465 -6.24616,0.4796 -1.01936,-0.009 -2.04028,-0.009 -3.05767,-0.0692 -0.7624,-0.0608 -1.51514,-0.17024 -2.26123,-0.31722 -0.29618,-0.0664 -0.59129,-0.13499 -0.8806,-0.21962 -0.37559,-0.26274 -0.72063,-0.55502 -1.03079,-0.88009 -0.844,-0.87638 -1.50132,-1.86401 -2.25907,-2.79325 -0.69442,-0.90371 -1.42252,-1.79801 -1.75996,-2.85416 -0.38186,-1.09234 -0.53068,-2.2299 -0.62781,-3.36392 -0.0571,-1.57894 0.0511,-3.16174 0.28626,-4.72856 0.40342,-2.24562 1.00354,-4.47123 1.97896,-6.59254 0.91422,-1.85455 1.91476,-3.68668 3.2567,-5.35205 1.14641,-1.45221 2.37792,-2.85438 3.5282,-4.30439 1.08687,-1.45209 2.18372,-2.91345 2.94986,-4.51231 0.58696,-1.36248 1.10902,-2.76135 1.22613,-4.21772 0.0245,-0.93137 -0.0843,-1.84035 -0.48345,-2.71702 -0.63207,-1.34862 -0.99728,-1.65708 -3.03247,-2.36779 -0.84054,-0.15908 -1.68788,0.0639 -2.51462,0.19153 -1.88741,0.33839 -3.76745,0.70803 -5.63496,1.11844 -1.94151,0.44412 -3.86383,0.9554 -5.68131,1.69512 -2.08386,0.77987 -4.17356,1.55471 -6.19672,2.4435 -1.10199,0.48008 -2.20927,0.95841 -3.24595,1.5369 -1.27614,0.71213 -2.44767,1.5514 -3.63234,2.36519 -2.34372,1.68222 -4.62828,3.41499 -6.84602,5.2162 -0.58865,0.4781 -1.18685,0.94872 -1.75475,1.44451 -0.48547,0.42387 -0.93596,0.87535 -1.40397,1.3131 -2.72514,2.69354 -5.22693,5.54024 -7.81385,8.32878 -2.23188,2.41076 -4.41099,4.85786 -6.72134,7.21474 -2.09773,2.0491 -4.21204,4.08998 -6.54747,5.95031 -2.27495,1.7965 -4.58636,3.58673 -7.27144,4.94115 -1.19,0.64944 -0.89193,0.49733 -2.11471,1.12184 -0.85535,0.43686 -1.7183,0.89429 -2.64855,1.21161 -0.16735,0.0571 -0.3442,0.0914 -0.51631,0.13714 -0.31855,0.0506 -0.64117,0.0932 -0.96167,0.0997 -0.009,-0.022 -0.0181,-0.0444 -0.0261,-0.0672 -0.17102,-0.65473 -0.0238,-1.32573 0.0869,-1.98084 0.3068,-1.3001 0.86557,-2.53752 1.36834,-3.79017 0.92526,-2.10855 2.08959,-4.13789 3.49473,-6.04868 2.07064,-2.84527 4.16483,-5.67842 6.26001,-8.51066 2.19015,-2.9059 4.40614,-5.79705 6.70749,-8.64105 2.2983,-2.83867 4.50439,-5.72755 6.71919,-8.6132 2.10371,-2.83421 4.22215,-5.66383 6.04007,-8.64025 0.26563,-0.42276 0.5328,-0.84497 0.79705,-1.26834 0.3087,-0.49461 0.60422,-1.00399 0.93803,-1.49213 0.10664,-0.11187 0.21314,-0.22378 0.31975,-0.3357 0.26092,-0.28629 0.51326,-0.57821 0.75312,-0.87747 0.10809,-0.13486 0.17054,-0.21708 0.16617,-0.23081 v 0 0 0 0 0 0 c -0.4949,-0.23587 -1.00654,-0.44515 -1.517,-0.65629 -0.18633,-0.10115 -0.37352,-0.20162 -0.56148,-0.30056 0,0 -0.002,0 -0.002,-8.9e-4 -1.4e-4,-3e-5 0,2e-5 0,0 v 0 0 z m -181.78089,0.1439 c 1.10395,0.009 2.19031,0.1291 3.2487,0.46789 0.12518,0.0423 0.23432,0.0794 0.32773,0.11137 0.25622,0.19897 0.48937,0.42029 0.72027,0.63859 0.2179,0.25509 0.38018,0.40296 0.49543,0.71068 0.33549,0.89581 0.1909,1.88002 0.29731,2.79846 0.0245,1.04912 0.0639,2.13183 -0.12654,3.17761 -0.099,0.26066 -0.19459,0.5178 -0.27243,0.73618 -0.85537,1.15899 -1.72945,2.3098 -2.73884,3.3785 -1.38289,1.44306 -2.78663,2.86677 -4.27306,4.23516 -2.47894,2.31481 -4.89154,4.67931 -7.1756,7.13487 -2.02322,2.1463 -4.06379,4.28062 -6.06343,6.44238 -1.9547,2.00887 -3.88192,4.03691 -5.84196,6.04192 -2.2154,2.23088 -4.48391,4.43334 -7.05735,6.37708 -2.00125,1.46485 -4.03318,2.89924 -6.0671,4.33144 -1.79617,0.93024 -3.60774,1.83961 -5.41379,2.75553 -1.90971,0.91044 -3.82004,1.84519 -5.87514,2.50285 -2.08496,0.62671 -4.14576,1.33602 -6.31958,1.71931 -1.47692,0.28567 -2.97305,0.47518 -4.47546,0.63703 -0.3449,0.0321 -0.69104,0.0535 -1.03756,0.0711 -0.418,-0.59568 -0.50832,-1.38657 -0.78382,-2.00608 -0.50119,-1.79634 -0.71071,-3.62125 -0.54766,-5.46916 0.12911,-2.29696 0.267,-4.61915 1.01452,-6.8434 0.50192,-1.61708 1.03279,-3.25054 2.04222,-4.70174 1.78651,-2.28443 3.64525,-4.54026 5.88497,-6.52801 3.28909,-2.82335 6.77973,-5.46716 10.32202,-8.05996 3.047,-2.30926 6.03311,-4.68489 9.29462,-6.78016 2.41613,-1.52909 4.95786,-2.90812 7.36328,-4.45064 2.57546,-1.60958 5.15943,-3.21117 7.87038,-4.6557 2.14905,-1.10684 4.3156,-2.19795 6.6304,-3.04229 1.09322,-0.42168 2.19578,-0.81167 3.35161,-1.09632 1.32957,-0.33937 2.71008,-0.5413 4.09644,-0.611 0.3715,-0.0174 0.74143,-0.0266 1.10942,-0.0235 z m 2.60461,0.0154 c -0.17761,-0.0917 0.36675,0.16757 0.53781,0.26778 0.0183,0.0107 0.0364,0.0215 0.0543,0.0325 -0.17195,-0.0857 -0.36972,-0.18551 -0.59218,-0.3003 z m 120.51707,1.96574 c 0.18085,0 0.36246,0.005 0.54519,0.0179 0.0241,0.0465 0.0459,0.0939 0.0651,0.14181 0.26995,1.4855 -0.0588,2.9719 -0.50188,4.4129 -0.66046,1.85642 -1.50149,3.65568 -2.67648,5.3276 -1.46687,2.12978 -3.11533,4.16497 -4.99024,6.05413 -1.92682,1.80822 -3.87975,3.6046 -6.11656,5.14544 -1.86376,1.30115 -3.7408,2.61715 -5.95837,3.46932 -2.72218,1.117 -5.55328,2.05855 -8.54426,2.51091 -3.18375,0.47288 -6.40553,0.71176 -9.61588,1.02165 0.47577,-1.42332 1.08826,-2.80701 1.93501,-4.11989 1.35816,-2.17918 2.83114,-4.31547 4.61215,-6.26935 1.77997,-1.93714 3.48998,-3.92543 5.40611,-5.76918 1.55523,-1.50998 3.16364,-2.97697 5.02066,-4.22841 1.59556,-1.10407 3.21089,-2.20237 4.97273,-3.11203 1.7078,-0.87873 3.49319,-1.64753 5.23412,-2.47657 1.09315,-0.45623 2.2299,-0.87869 3.44621,-1.02242 1.05588,-0.13511 2.1296,-0.20336 3.15195,-0.48505 0.78191,-0.21651 1.58469,-0.35264 2.39791,-0.4528 0.53832,-0.10622 1.07397,-0.16463 1.61652,-0.16603 z m -276.520218,3.83261 c 0.143166,0.0561 0.285809,0.11349 0.412506,0.18762 0.03402,0.0199 0.05053,0.0307 0.05279,0.0341 v 0 h 3.16e-4 v 0 h -3.16e-4 v 0 h -3.16e-4 -3.07e-4 -3.16e-4 c -0.01696,0 -0.235941,-0.10676 -0.464411,-0.22274 z m 376.859488,0.33828 c 0.0438,0.0128 0.0874,0.0269 0.13051,0.0429 0.43815,0.2662 0.28985,0.17367 -0.13051,-0.0429 z m -215.63174,2.03861 c 0.0337,-0.004 0.0462,0.1228 0.009,0.10956 -0.0348,-0.0123 -0.069,-0.0248 -0.1026,-0.037 0.0245,-0.029 0.0523,-0.0542 0.0888,-0.0711 0.002,-8.9e-4 0.004,-0.002 0.005,-0.002 z m -2.20747,2.84296 c -0.0146,0.16175 -0.0295,0.32355 -0.0439,0.48533 -0.24401,0.21722 -0.48826,0.43428 -0.73287,0.65107 -0.26956,0.24362 -0.53807,0.48792 -0.80565,0.73305 0.56138,-0.60281 1.08329,-1.22956 1.58244,-1.86945 z m -12.59984,12.67062 c -0.52101,0.56911 -1.03869,1.14056 -1.55878,1.71151 -0.34524,0.35144 -0.69064,0.70277 -1.0351,1.05469 0.66066,-0.70511 1.32201,-1.40958 1.98325,-2.11433 0.20259,-0.21799 0.40611,-0.43526 0.61063,-0.65187 z m -6.86508,7.34486 c -0.0969,0.11244 -0.19343,0.22519 -0.28934,0.33829 -1.27664,1.38197 -2.58389,2.7428 -3.97358,4.0426 1.42052,-1.46048 2.83553,-2.92519 4.26292,-4.38089 z" /> diff --git a/dist/qt_themes/default/icons/256x256/eden.png b/dist/qt_themes/default/icons/256x256/eden.png index 1ea2d8ea5d..2fb04d8bc2 100644 Binary files a/dist/qt_themes/default/icons/256x256/eden.png and b/dist/qt_themes/default/icons/256x256/eden.png differ diff --git a/dist/qt_themes/default/icons/256x256/eden_named.png b/dist/qt_themes/default/icons/256x256/eden_named.png index 086d338112..a349ac7ac8 100644 Binary files a/dist/qt_themes/default/icons/256x256/eden_named.png and b/dist/qt_themes/default/icons/256x256/eden_named.png differ diff --git a/docs/CPMUtil/AddCIPackage.md b/docs/CPMUtil/AddCIPackage.md index bcd72e10da..bc7c1ccfad 100644 --- a/docs/CPMUtil/AddCIPackage.md +++ b/docs/CPMUtil/AddCIPackage.md @@ -7,13 +7,14 @@ - `EXTENSION`: Artifact extension (default `tar.zst`) - `MIN_VERSION`: Minimum version for `find_package`. Only used if platform does not support this package as a bundled artifact - `DISABLED_PLATFORMS`: List of platforms that lack artifacts for this package. Options: - * `windows-amd64` - * `windows-arm64` - * `mingw-amd64` - * `mingw-arm64` - * `android` - * `solaris-amd64` - * `freebsd-amd64` - * `linux-amd64` - * `linux-aarch64` - * `macos-universal` \ No newline at end of file + - `windows-amd64` + - `windows-arm64` + - `mingw-amd64` + - `mingw-arm64` + - `android-x86_64` + - `android-aarch64` + - `solaris-amd64` + - `freebsd-amd64` + - `linux-amd64` + - `linux-aarch64` + - `macos-universal` diff --git a/docs/CPMUtil/AddJsonPackage.md b/docs/CPMUtil/AddJsonPackage.md index d8719eec0a..464cd1731b 100644 --- a/docs/CPMUtil/AddJsonPackage.md +++ b/docs/CPMUtil/AddJsonPackage.md @@ -22,11 +22,11 @@ If `ci` is `false`: - `sha` -> `SHA` - `key` -> `KEY` - `tag` -> `TAG` - * If the tag contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified + - If the tag contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified - `url` -> `URL` - `artifact` -> `ARTIFACT` - * If the artifact contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified - * If the artifact contains `%TAG%`, that part will be replaced by the `tag` (with its replacement already done) + - If the artifact contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified + - If the artifact contains `%TAG%`, that part will be replaced by the `tag` (with its replacement already done) - `git_version` -> `GIT_VERSION` - `git_host` -> `GIT_HOST` - `source_subdir` -> `SOURCE_SUBDIR` @@ -101,4 +101,4 @@ In order: OpenSSL CI, Boost (tag + artifact), Opus (options + find_args), discor ] } } -``` \ No newline at end of file +``` diff --git a/docs/CPMUtil/AddPackage.md b/docs/CPMUtil/AddPackage.md index 410c84a958..6e9ae1b775 100644 --- a/docs/CPMUtil/AddPackage.md +++ b/docs/CPMUtil/AddPackage.md @@ -17,7 +17,7 @@ - `URL`: The URL to fetch. - `REPO`: The repo to use (`owner/repo`). - `GIT_HOST`: The Git host to use - * Defaults to `github.com`. Do not include the protocol, as HTTPS is enforced. + - Defaults to `github.com`. Do not include the protocol, as HTTPS is enforced. - `TAG`: The tag to fetch, if applicable. - `ARTIFACT`: The name of the artifact, if applicable. - `SHA`: Commit sha to fetch, if applicable. @@ -26,23 +26,23 @@ The following configurations are supported, in descending order of precedence: - `URL`: Bare URL download, useful for custom artifacts - * If this is set, `GIT_URL` or `REPO` should be set to allow the dependency viewer to link to the project's Git repository. - * If this is NOT set, `REPO` must be defined. + - If this is set, `GIT_URL` or `REPO` should be set to allow the dependency viewer to link to the project's Git repository. + - If this is NOT set, `REPO` must be defined. - `REPO + TAG + ARTIFACT`: GitHub release artifact - * The final download URL will be `https://github.com/${REPO}/releases/download/${TAG}/${ARTIFACT}` - * Useful for prebuilt libraries and prefetched archives + - The final download URL will be `https://github.com/${REPO}/releases/download/${TAG}/${ARTIFACT}` + - Useful for prebuilt libraries and prefetched archives - `REPO + TAG`: GitHub tag archive - * The final download URL will be `https://github.com/${REPO}/archive/refs/tags/${TAG}.tar.gz` - * Useful for pinning to a specific tag, better for build identification + - The final download URL will be `https://github.com/${REPO}/archive/refs/tags/${TAG}.tar.gz` + - Useful for pinning to a specific tag, better for build identification - `REPO + SHA`: GitHub commit archive - * The final download URL will be `https://github.com/${REPO}/archive/${SHA}.zip` - * Useful for pinning to a specific commit + - The final download URL will be `https://github.com/${REPO}/archive/${SHA}.zip` + - Useful for pinning to a specific commit - `REPO + BRANCH`: GitHub branch archive - * The final download URL will be `https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip` - * Generally not recommended unless the branch is frozen + - The final download URL will be `https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip` + - Generally not recommended unless the branch is frozen - `REPO`: GitHub master archive - * The final download URL will be `https://github.com/${REPO}/archive/refs/heads/master.zip` - * Generally not recommended unless the project is dead + - The final download URL will be `https://github.com/${REPO}/archive/refs/heads/master.zip` + - Generally not recommended unless the project is dead ## Hashing @@ -54,20 +54,20 @@ Hashing strategies, descending order of precedence: - `HASH`: Bare hash verification, useful for static downloads e.g. commit archives - `HASH_SUFFIX`: Download the hash as `${DOWNLOAD_URL}.${HASH_SUFFIX}` - * The downloaded hash *must* match the hash algorithm and contain nothing but the hash; no filenames or extra content. + - The downloaded hash *must* match the hash algorithm and contain nothing but the hash; no filenames or extra content. - `HASH_URL`: Download the hash from a separate URL ## Other Options - `KEY`: Custom cache key to use (stored as `.cache/cpm/${packagename_lower}/${key}`) - * Default is based on, in descending order of precedence: + - Default is based on, in descending order of precedence: - First 4 characters of the sha - `GIT_VERSION` - Tag - `VERSION` - Otherwise, CPM defaults will be used. This is not recommended as it doesn't produce reproducible caches - `DOWNLOAD_ONLY`: Whether or not to configure the downloaded package via CMake - * Useful to turn `OFF` if the project doesn't use CMake + - Useful to turn `OFF` if the project doesn't use CMake - `SOURCE_SUBDIR`: Subdirectory of the project containing a CMakeLists.txt file - `FIND_PACKAGE_ARGUMENTS`: Arguments to pass to the `find_package` call - `BUNDLED_PACKAGE`: Set to `ON` to default to the bundled package @@ -80,12 +80,14 @@ Hashing strategies, descending order of precedence: For each added package, users may additionally force usage of the system/bundled package. +- `${package}_DIR`: Path to a separately-downloaded copy of the package. Note that versioning is not checked! - `${package}_FORCE_SYSTEM`: Require the package to be installed on the system - `${package}_FORCE_BUNDLED`: Force the package to be fetched and use the bundled version ## System/Bundled Packages Descending order of precedence: + - If `${package}_FORCE_SYSTEM` is true, requires the package to be on the system - If `${package}_FORCE_BUNDLED` is true, forcefully uses the bundled package - If `CPMUTIL_FORCE_SYSTEM` is true, requires the package to be on the system @@ -101,8 +103,8 @@ URLs: - `GIT_URL` - `REPO` as a Git repository - * You may optionally specify `GIT_HOST` to use a custom host, e.g. `GIT_HOST git.crueter.xyz`. Note that the git host MUST be GitHub-like in its artifact/archive downloads, e.g. Forgejo - * If `GIT_HOST` is unspecified, defaults to `github.com` + - You may optionally specify `GIT_HOST` to use a custom host, e.g. `GIT_HOST git.crueter.xyz`. Note that the git host MUST be GitHub-like in its artifact/archive downloads, e.g. Forgejo + - If `GIT_HOST` is unspecified, defaults to `github.com` - `URL` Versions (bundled): @@ -113,4 +115,4 @@ Versions (bundled): - `TAG` - "unknown" -If the package is a system package, AddPackage will attempt to determine the package version and append ` (system)` to the identifier. Otherwise, it will be marked as `unknown (system)` +If the package is a system package, AddPackage will attempt to determine the package version and append `(system)` to the identifier. Otherwise, it will be marked as `unknown (system)` diff --git a/docs/CPMUtil/AddQt.md b/docs/CPMUtil/AddQt.md new file mode 100644 index 0000000000..e9595b8004 --- /dev/null +++ b/docs/CPMUtil/AddQt.md @@ -0,0 +1,28 @@ +# AddQt + +Simply call `AddQt()` before any Qt `find_package` calls and everything will be set up for you. On Linux, the bundled Qt library is built as a shared library, and provided you have OpenSSL and X11, everything should just work. + +On Windows, MinGW, and MacOS, Qt is bundled as a static library. No further action is needed, as the provided libraries automatically integrate the Windows/Cocoa plugins, alongside the corresponding Multimedia and Network plugins. + +## Modules + +The following modules are bundled into these Qt builds: + +- Base (Gui, Core, Widgets, Network) +- Multimedia +- Declarative (Quick, QML) +- Linux: Wayland client + +Each platform has the corresponding QPA built in and set as the default as well. This means you don't need to add `Q_IMPORT_PLUGIN`! + +## Example + +See an example in the [`tests/qt`](https://git.crueter.xyz/CMake/CPMUtil/src/branch/master/tests/qt/CMakeLists.txt) directory. + +## Versions + +The following versions have available builds: + +- 6.9.3 + +See [`crueter-ci/Qt`](https://github.com/crueter-ci/Qt) for an updated list at any time. diff --git a/docs/CPMUtil/README.md b/docs/CPMUtil/README.md index 3f0cb2c741..ff19cb4f76 100644 --- a/docs/CPMUtil/README.md +++ b/docs/CPMUtil/README.md @@ -5,7 +5,7 @@ CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful u Global Options: - `CPMUTIL_FORCE_SYSTEM` (default `OFF`): Require all CPM dependencies to use system packages. NOT RECOMMENDED! - * You may optionally override this (section) + - You may optionally override this (section) - `CPMUTIL_FORCE_BUNDLED` (default `ON` on MSVC and Android, `OFF` elsewhere): Require all CPM dependencies to use bundled packages. You are highly encouraged to read AddPackage first, even if you plan to only interact with CPMUtil via `AddJsonPackage`. @@ -13,6 +13,7 @@ You are highly encouraged to read AddPackage first, even if you plan to only int - [AddPackage](#addpackage) - [AddCIPackage](#addcipackage) - [AddJsonPackage](#addjsonpackage) +- [AddQt](#addqt) - [Lists](#lists) - [For Packagers](#for-packagers) - [Network Sandbox](#network-sandbox) @@ -30,6 +31,10 @@ The core of CPMUtil is the [`AddPackage`](./AddPackage.md) function. [`AddPackag [`AddJsonPackage`](./AddJsonPackage.md) is the recommended method of usage for CPMUtil. +## AddQt + +[`AddQt`](./AddQt.md) adds a specific version of Qt to your project. + ## Lists CPMUtil will create three lists of dependencies where `AddPackage` or similar was used. Each is in order of addition. @@ -37,8 +42,8 @@ CPMUtil will create three lists of dependencies where `AddPackage` or similar wa - `CPM_PACKAGE_NAMES`: The names of packages included by CPMUtil - `CPM_PACKAGE_URLS`: The URLs to project/repo pages of packages - `CPM_PACKAGE_SHAS`: Short version identifiers for each package - * If the package was included as a system package, ` (system)` is appended thereafter - * Packages whose versions can't be deduced will be left as `unknown`. + - If the package was included as a system package, `(system)` is appended thereafter + - Packages whose versions can't be deduced will be left as `unknown`. For an example of how this might be implemented in an application, see Eden's implementation: @@ -54,6 +59,8 @@ If you are packaging a project that uses CPMUtil, read this! For sandboxed environments (e.g. Gentoo, nixOS) you must install all dependencies to the system beforehand and set `-DCPMUTIL_FORCE_SYSTEM=ON`. If a dependency is missing, get creating! +Alternatively, if CPMUtil pulls in a package that has no suitable way to install or use a system version, download it separately and pass `-DPackageName_DIR=/path/to/downloaded/dir` (e.g. shaders) + ### Unsandboxed -For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. That is pretty much half the reason I created this behemoth, after all. \ No newline at end of file +For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. That is pretty much half the reason I created this behemoth, after all. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 18491962b2..185be585d3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -25,7 +25,7 @@ set(BUILD_SHARED_LIBS OFF) # Skip install rules for all externals set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON) -# Xbyak (also used by Dynarmic, so needs to be added first) +# Xbyak if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) if (PLATFORM_SUN OR PLATFORM_OPENBSD OR PLATFORM_NETBSD OR PLATFORM_DRAGONFLY) AddJsonPackage(xbyak_sun) @@ -34,11 +34,19 @@ if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) endif() endif() -# Oaknut (also used by Dynarmic, so needs to be added first) +# Oaknut if (ARCHITECTURE_arm64 OR DYNARMIC_TESTS) AddJsonPackage(oaknut) endif() +# biscuit +if (ARCHITECTURE_riscv64) + AddJsonPackage(biscuit) +endif() + +# mcl +AddJsonPackage(mcl) + # enet AddJsonPackage(enet) diff --git a/externals/cmake-modules/DefaultConfig.cmake b/externals/cmake-modules/DefaultConfig.cmake index eb5bb3cdef..81e93f6c2e 100644 --- a/externals/cmake-modules/DefaultConfig.cmake +++ b/externals/cmake-modules/DefaultConfig.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## DefaultConfig ## diff --git a/externals/cmake-modules/DetectArchitecture.cmake b/externals/cmake-modules/DetectArchitecture.cmake index 82dce31878..105963c8c2 100644 --- a/externals/cmake-modules/DetectArchitecture.cmake +++ b/externals/cmake-modules/DetectArchitecture.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## DetectArchitecture ## #[[ @@ -93,133 +93,129 @@ function(detect_architecture_symbols) endforeach() endfunction() -function(DetectArchitecture) - # arches here are put in a sane default order of importance - # notably, amd64, arm64, and riscv (in order) are BY FAR the most common - # mips is pretty popular in embedded - # ppc64 is pretty popular in supercomputing - # sparc is uh - # ia64 exists - # the rest exist, but are probably less popular than ia64 +# arches here are put in a sane default order of importance +# notably, amd64, arm64, and riscv (in order) are BY FAR the most common +# mips is pretty popular in embedded +# ppc64 is pretty popular in supercomputing +# sparc is uh +# ia64 exists +# the rest exist, but are probably less popular than ia64 - detect_architecture_symbols( - ARCH arm64 - SYMBOLS - "__ARM64__" - "__aarch64__" - "_M_ARM64") +detect_architecture_symbols( + ARCH arm64 + SYMBOLS + "__ARM64__" + "__aarch64__" + "_M_ARM64") - detect_architecture_symbols( - ARCH x86_64 - SYMBOLS - "__x86_64" - "__x86_64__" - "__amd64" - "_M_X64" - "_M_AMD64") +detect_architecture_symbols( + ARCH x86_64 + SYMBOLS + "__x86_64" + "__x86_64__" + "__amd64" + "_M_X64" + "_M_AMD64") - # riscv is interesting since it generally does not define a riscv64-specific symbol - # We can, however, check for the rv32 zcf extension which is good enough of a heuristic on GCC - detect_architecture_symbols( - ARCH riscv - SYMBOLS - "__riscv_zcf") +# riscv is interesting since it generally does not define a riscv64-specific symbol +# We can, however, check for the rv32 zcf extension which is good enough of a heuristic on GCC +detect_architecture_symbols( + ARCH riscv + SYMBOLS + "__riscv_zcf") - # if zcf doesn't exist we can safely assume it's riscv64 - detect_architecture_symbols( - ARCH riscv64 - SYMBOLS - "__riscv") +# if zcf doesn't exist we can safely assume it's riscv64 +detect_architecture_symbols( + ARCH riscv64 + SYMBOLS + "__riscv") - detect_architecture_symbols( - ARCH x86 - SYMBOLS - "__i386" - "__i386__" - "_M_IX86") +detect_architecture_symbols( + ARCH x86 + SYMBOLS + "__i386" + "__i386__" + "_M_IX86") - detect_architecture_symbols( - ARCH arm - SYMBOLS - "__arm__" - "__TARGET_ARCH_ARM" - "_M_ARM") +detect_architecture_symbols( + ARCH arm + SYMBOLS + "__arm__" + "__TARGET_ARCH_ARM" + "_M_ARM") - detect_architecture_symbols( - ARCH ia64 - SYMBOLS - "__ia64" - "__ia64__" - "_M_IA64") +detect_architecture_symbols( + ARCH ia64 + SYMBOLS + "__ia64" + "__ia64__" + "_M_IA64") - # mips is probably the least fun to detect due to microMIPS - # Because microMIPS is such cancer I'm considering it out of scope for now - detect_architecture_symbols( - ARCH mips64 - SYMBOLS - "__mips64") +# mips is probably the least fun to detect due to microMIPS +# Because microMIPS is such cancer I'm considering it out of scope for now +detect_architecture_symbols( + ARCH mips64 + SYMBOLS + "__mips64") - detect_architecture_symbols( - ARCH mips - SYMBOLS - "__mips" - "__mips__" - "_M_MRX000") +detect_architecture_symbols( + ARCH mips + SYMBOLS + "__mips" + "__mips__" + "_M_MRX000") - detect_architecture_symbols( - ARCH ppc64 - SYMBOLS - "__ppc64__" - "__powerpc64__" - "_ARCH_PPC64" - "_M_PPC64") +detect_architecture_symbols( + ARCH ppc64 + SYMBOLS + "__ppc64__" + "__powerpc64__" + "_ARCH_PPC64" + "_M_PPC64") - detect_architecture_symbols( - ARCH ppc - SYMBOLS - "__ppc__" - "__ppc" - "__powerpc__" - "_ARCH_COM" - "_ARCH_PWR" - "_ARCH_PPC" - "_M_MPPC" - "_M_PPC") +detect_architecture_symbols( + ARCH ppc + SYMBOLS + "__ppc__" + "__ppc" + "__powerpc__" + "_ARCH_COM" + "_ARCH_PWR" + "_ARCH_PPC" + "_M_MPPC" + "_M_PPC") - detect_architecture_symbols( - ARCH sparc64 - SYMBOLS - "__sparc_v9__") +detect_architecture_symbols( + ARCH sparc64 + SYMBOLS + "__sparc_v9__") - detect_architecture_symbols( - ARCH sparc - SYMBOLS - "__sparc__" - "__sparc") +detect_architecture_symbols( + ARCH sparc + SYMBOLS + "__sparc__" + "__sparc") - # I don't actually know about loongarch32 since crossdev does not support it, only 64 - detect_architecture_symbols( - ARCH loongarch64 - SYMBOLS - "__loongarch__" - "__loongarch64") +# I don't actually know about loongarch32 since crossdev does not support it, only 64 +detect_architecture_symbols( + ARCH loongarch64 + SYMBOLS + "__loongarch__" + "__loongarch64") - detect_architecture_symbols( - ARCH wasm - SYMBOLS - "__EMSCRIPTEN__") +detect_architecture_symbols( + ARCH wasm + SYMBOLS + "__EMSCRIPTEN__") - # "generic" target - # If you have reached this point, you're on some as-of-yet unsupported architecture. - # See the docs up above for known unsupported architectures - # If you're not in the list... I think you know what you're doing. - if (NOT DEFINED ARCHITECTURE) - set(ARCHITECTURE "GENERIC") - set(ARCHITECTURE_GENERIC 1) - add_definitions(-DARCHITECTURE_GENERIC=1) - endif() +# "generic" target +# If you have reached this point, you're on some as-of-yet unsupported architecture. +# See the docs up above for known unsupported architectures +# If you're not in the list... I think you know what you're doing. +if (NOT DEFINED ARCHITECTURE) + set(ARCHITECTURE "GENERIC") + set(ARCHITECTURE_GENERIC 1) + add_definitions(-DARCHITECTURE_GENERIC=1) +endif() - message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}") - set(ARCHITECTURE "${ARCHITECTURE}" PARENT_SCOPE) - set(ARCHITECTURE_${ARCHITECTURE} 1 PARENT_SCOPE) -endfunction() +message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}") \ No newline at end of file diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index 25b324a674..6475884f1f 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## DetectPlatform ## @@ -147,5 +147,6 @@ endif() # awesome if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib") -endif() \ No newline at end of file + set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib") +endif() diff --git a/externals/cmake-modules/FasterLinker.cmake b/externals/cmake-modules/FasterLinker.cmake index 6b472a4f0f..37d8bca33b 100644 --- a/externals/cmake-modules/FasterLinker.cmake +++ b/externals/cmake-modules/FasterLinker.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## FasterLinker ## @@ -12,47 +12,45 @@ - mold (GCC only) - generally does well on GCC - lld - preferred on clang - bfd - the final fallback - - If none are found (macOS uses ld.prime, etc) just use the default linker + - If none are found just use the default linker ]] # This module is based on the work of Yuzu, specifically Liam White, # and later extended by crueter. -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CXX_GCC ON) -endif() +option(USE_FASTER_LINKER "Attempt to use a faster linker program" OFF) -find_program(LINKER_BFD bfd) -if (LINKER_BFD) - set(LINKER bfd) -endif() +if (USE_FASTER_LINKER) + macro(find_linker ld) + find_program(LINKER_${ld} ld.${ld}) + if (LINKER_${ld}) + set(LINKER ${ld}) + endif() + endmacro() -find_program(LINKER_LLD lld) -if (LINKER_LLD) - set(LINKER lld) -endif() + find_linker(bfd) + find_linker(lld) -if (CXX_GCC) - find_program(LINKER_MOLD mold) - if (LINKER_MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1") - set(LINKER mold) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_program(LINKER_MOLD mold) + if (LINKER_MOLD AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1") + set(LINKER mold) + endif() + + find_linker(gold) + + if (LINKER STREQUAL "lld") + message(WARNING + "[FasterLinker] Using lld on GCC may cause issues.\ + Install mold, gold, or disable USE_FASTER_LINKER.") + endif() endif() - find_program(LINKER_GOLD gold) - if (LINKER_GOLD) - set(LINKER gold) + if (LINKER) + message(NOTICE "[FasterLinker] Selecting ${LINKER} as linker") + add_link_options("-fuse-ld=${LINKER}") + else() + message(WARNING "[FasterLinker] No faster linker found--using default") endif() -endif() - -if (LINKER) - message(NOTICE "[FasterLinker] Selecting ${LINKER} as linker") - add_link_options("-fuse-ld=${LINKER}") -else() - message(WARNING "[FasterLinker] No faster linker found--using default") -endif() - -if (LINKER STREQUAL "lld" AND CXX_GCC) - message(WARNING - "[FasterLinker] Using lld on GCC may cause issues " - "with certain LTO settings.") -endif() +endif() \ No newline at end of file diff --git a/externals/cmake-modules/GetSCMRev.cmake b/externals/cmake-modules/GetSCMRev.cmake index 4b144584e8..74c32097da 100644 --- a/externals/cmake-modules/GetSCMRev.cmake +++ b/externals/cmake-modules/GetSCMRev.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## GetSCMRev ## # Name is self explanatory. Gets revision information from files, OR from git. @@ -13,6 +13,8 @@ find_package(Git QUIET) # tag: git describe --tags --abbrev=0 # branch: git rev-parse --abbrev-ref=HEAD +# TODO: string overrides + function(run_git_command variable) if(NOT GIT_FOUND) set(${variable} "GIT-NOTFOUND" PARENT_SCOPE) diff --git a/externals/cmake-modules/UseCcache.cmake b/externals/cmake-modules/UseCcache.cmake index 8caea18fe0..34a9de3cf2 100644 --- a/externals/cmake-modules/UseCcache.cmake +++ b/externals/cmake-modules/UseCcache.cmake @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## UseCcache ## diff --git a/externals/cmake-modules/UseLTO.cmake b/externals/cmake-modules/UseLTO.cmake index d1603aba1c..b364ef22d8 100644 --- a/externals/cmake-modules/UseLTO.cmake +++ b/externals/cmake-modules/UseLTO.cmake @@ -1,17 +1,21 @@ # SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later ## UseLTO ## # Enable Interprocedural Optimization (IPO). # Self-explanatory. -include(CheckIPOSupported) -check_ipo_supported(RESULT COMPILER_SUPPORTS_LTO) -if(NOT COMPILER_SUPPORTS_LTO) - message(FATAL_ERROR - "Your compiler does not support interprocedural optimization" - " (IPO).") -endif() -set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO}) +option(ENABLE_LTO "Enable Link-Time Optimization (LTO)" OFF) + +if (ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT COMPILER_SUPPORTS_LTO) + if(NOT COMPILER_SUPPORTS_LTO) + message(FATAL_ERROR + "Your compiler does not support interprocedural optimization" + " (IPO). Disable ENABLE_LTO and try again.") + endif() + set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO}) +endif() \ No newline at end of file diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 56040bdb23..7ff4fb4eae 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -23,7 +23,7 @@ "package": "sirit", "name": "sirit", "repo": "eden-emulator/sirit", - "version": "1.0.3" + "version": "1.0.4" }, "httplib": { "repo": "yhirose/cpp-httplib", @@ -225,5 +225,57 @@ "hash": "e87ec14ed3e826d578ebf095c41580069dda603792ba91efa84f45f4571a28f4d91889675055fd6f042d7dc25b0b9443daf70963ae463e38b11bcba95f4c65a9", "version": "1.7", "find_args": "MODULE" + }, + "biscuit": { + "repo": "lioncash/biscuit", + "tag": "v%VERSION%", + "hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a", + "version": "0.9.1", + "git_version": "0.19.0" + }, + "mcl": { + "version": "0.1.12", + "repo": "azahar-emu/mcl", + "sha": "7b08d83418", + "hash": "9c6ba624cb22ef622f78046a82abb99bf5026284ba17dfacaf46ac842cbd3b0f515f5ba45a1598c7671318a78a2e648db72ce8d10e7537f34e39800bdcb57694", + "options": [ + "MCL_INSTALL OFF" + ], + "patches": [ + "0001-assert-macro.patch" + ] + }, + "libusb": { + "repo": "libusb/libusb", + "tag": "v%VERSION%", + "hash": "98c5f7940ff06b25c9aa65aa98e23de4c79a4c1067595f4c73cc145af23a1c286639e1ba11185cd91bab702081f307b973f08a4c9746576dc8d01b3620a3aeb5", + "find_args": "MODULE", + "git_version": "1.0.29", + "patches": [ + "0001-netbsd-gettime.patch" + ] + }, + "ffmpeg": { + "repo": "FFmpeg/FFmpeg", + "sha": "5e56937b74", + "hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b", + "bundled": true + }, + "ffmpeg-ci": { + "ci": true, + "package": "FFmpeg", + "name": "ffmpeg", + "repo": "crueter-ci/FFmpeg", + "version": "8.0.1-5e56937b74", + "min_version": "4.1" + }, + "tzdb": { + "package": "nx_tzdb", + "repo": "misc/tzdb_to_nx", + "git_host": "git.crueter.xyz", + "artifact": "%VERSION%.tar.gz", + "tag": "%VERSION%", + "hash": "dc37a189a44ce8b5c988ca550582431a6c7eadfd3c6e709bee6277116ee803e714333e85c9e6cbb5c69346a14d6f2cc7ed96e8aa09cc5fb8a89f945059651db6", + "version": "121125" } } diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 14438f1229..3140f8e545 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -4,8 +4,6 @@ # SPDX-FileCopyrightText: 2021 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -include(CPMUtil) - # TODO(crueter, MaranBr): Externals FFmpeg 8.0 set(FFmpeg_HWACCEL_LIBRARIES) diff --git a/externals/ffmpeg/cpmfile.json b/externals/ffmpeg/cpmfile.json deleted file mode 100644 index 405d060411..0000000000 --- a/externals/ffmpeg/cpmfile.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "ffmpeg": { - "repo": "FFmpeg/FFmpeg", - "sha": "5e56937b74", - "hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b", - "bundled": true - }, - "ffmpeg-ci": { - "ci": true, - "package": "FFmpeg", - "name": "ffmpeg", - "repo": "crueter-ci/FFmpeg", - "version": "8.0.1-5e56937b74", - "min_version": "4.1" - } -} diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index a53464ea98..7eae81d3b6 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -4,8 +4,6 @@ # SPDX-FileCopyrightText: 2020 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -include(CPMUtil) - AddJsonPackage(libusb) if (NOT libusb_ADDED) diff --git a/externals/libusb/cpmfile.json b/externals/libusb/cpmfile.json deleted file mode 100644 index 4abe225ab3..0000000000 --- a/externals/libusb/cpmfile.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "libusb": { - "repo": "libusb/libusb", - "tag": "v%VERSION%", - "hash": "98c5f7940ff06b25c9aa65aa98e23de4c79a4c1067595f4c73cc145af23a1c286639e1ba11185cd91bab702081f307b973f08a4c9746576dc8d01b3620a3aeb5", - "find_args": "MODULE", - "git_version": "1.0.29", - "patches": [ - "0001-netbsd-gettime.patch" - ] - } -} diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt index efb3e2b058..b9aa50acd5 100644 --- a/externals/nx_tzdb/CMakeLists.txt +++ b/externals/nx_tzdb/CMakeLists.txt @@ -4,8 +4,6 @@ # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -include(CPMUtil) - set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") add_library(nx_tzdb INTERFACE) diff --git a/externals/nx_tzdb/cpmfile.json b/externals/nx_tzdb/cpmfile.json deleted file mode 100644 index 908201564f..0000000000 --- a/externals/nx_tzdb/cpmfile.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tzdb": { - "package": "nx_tzdb", - "repo": "misc/tzdb_to_nx", - "git_host": "git.crueter.xyz", - "artifact": "%VERSION%.tar.gz", - "tag": "%VERSION%", - "hash": "dc37a189a44ce8b5c988ca550582431a6c7eadfd3c6e709bee6277116ee803e714333e85c9e6cbb5c69346a14d6f2cc7ed96e8aa09cc5fb8a89f945059651db6", - "version": "121125" - } -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c06ed56255..6cafe4882a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -16,6 +16,10 @@ endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:_DEBUG> $<$>:NDEBUG>) +if (YUZU_STATIC_BUILD) + add_compile_definitions(QT_STATICPLUGIN) +endif() + # Set compilation flags if (MSVC AND NOT CXX_CLANG) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) @@ -169,25 +173,18 @@ else() add_compile_definitions(_FILE_OFFSET_BITS=64) endif() - if (YUZU_STATIC_BUILD) - add_compile_definitions(QT_STATICPLUGIN) + if (YUZU_STATIC_BUILD AND NOT APPLE) + add_compile_options(-static) - # macos doesn't even let you make static executables... wtf? - if (NOT APPLE) - add_compile_options(-static) - if (YUZU_STATIC_BUILD) - # yuzu-cmd requires us to explicitly link libpthread, libgcc, and libstdc++ as static - # At a guess, it's probably because Qt handles the Qt executable for us, whereas this does not - add_link_options(-static -lpthread) - add_link_options(-static-libgcc -static-libstdc++) - endif() - endif() + # yuzu-cmd requires us to explicitly link libpthread, libgcc, and libstdc++ as static + add_link_options(-Wl,-Bstatic -static -lpthread) + add_link_options(-static-libgcc -static-libstdc++) endif() if (MINGW) add_compile_definitions(MINGW_HAS_SECURE_API) - # Only windows has this requirement, thanks windows + # Only windows has this requirement if (WIN32 AND ARCHITECTURE_x86_64) add_compile_options("-msse4.1") endif() diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 61e817e546..185704c8fe 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project @@ -112,6 +112,9 @@ android { } } + // The app name is constructed with the appNameSuffix and appNameBase manifest placeholders + // suffix is used for build type--remember to include a space beforehand + // Define build types, which are orthogonal to product flavors. buildTypes { // Signed by release key, allowing for upload to Play Store. @@ -122,6 +125,8 @@ android { signingConfigs.getByName("default") } + manifestPlaceholders += mapOf("appNameSuffix" to "") + isMinifyEnabled = true isDebuggable = false proguardFiles( @@ -140,6 +145,9 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + + manifestPlaceholders += mapOf("appNameSuffix" to " Debug Release") + versionNameSuffix = "-relWithDebInfo" applicationIdSuffix = ".relWithDebInfo" isJniDebuggable = true @@ -153,13 +161,20 @@ android { isJniDebuggable = true versionNameSuffix = "-debug" applicationIdSuffix = ".debug" + + manifestPlaceholders += mapOf("appNameSuffix" to " Debug") } } + // appNameBase is used for the primary identifier + // this should be "Eden " flavorDimensions.add("version") productFlavors { create("mainline") { dimension = "version" + isDefault = true + + manifestPlaceholders += mapOf("appNameBase" to "Eden") resValue("string", "app_name_suffixed", "Eden") ndk { @@ -169,6 +184,7 @@ android { create("genshinSpoof") { dimension = "version" + manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized") resValue("string", "app_name_suffixed", "Eden Optimized") applicationId = "com.miHoYo.Yuanshen" @@ -179,6 +195,7 @@ android { create("legacy") { dimension = "version" + manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy") resValue("string", "app_name_suffixed", "Eden Legacy") applicationId = "dev.legacy.eden_emulator" @@ -201,7 +218,8 @@ android { create("chromeOS") { dimension = "version" - resValue("string", "app_name_suffixed", "Eden") + manifestPlaceholders += mapOf("appNameBase" to "Eden ChromeOS") + resValue("string", "app_name_suffixed", "Eden ChromeOS") ndk { abiFilters += listOf("x86_64") @@ -215,31 +233,6 @@ android { } } - // this is really annoying but idk any other ways to fix this behavior - applicationVariants.all { - val variant = this - when { - variant.flavorName == "legacy" && variant.buildType.name == "debug" -> { - variant.resValue("string", "app_name_suffixed", "Eden Legacy Debug") - } - variant.flavorName == "mainline" && variant.buildType.name == "debug" -> { - variant.resValue("string", "app_name_suffixed", "Eden Debug") - } - variant.flavorName == "genshinSpoof" && variant.buildType.name == "debug" -> { - variant.resValue("string", "app_name_suffixed", "Eden Optimized Debug") - } - variant.flavorName == "legacy" && variant.buildType.name == "relWithDebInfo" -> { - variant.resValue("string", "app_name_suffixed", "Eden Legacy Debug Release") - } - variant.flavorName == "mainline" && variant.buildType.name == "relWithDebInfo" -> { - variant.resValue("string", "app_name_suffixed", "Eden Debug Release") - } - variant.flavorName == "genshinSpoof" && variant.buildType.name == "relWithDebInfo" -> { - variant.resValue("string", "app_name_suffixed", "Eden Optimized Debug Release") - } - } - } - externalNativeBuild { cmake { version = "3.22.1" @@ -252,6 +245,9 @@ idea { module { // Inclusion to exclude build/ dir from non-Android excludeDirs.add(file("${edenDir}/build")) + + // also exclude CPM cache from automatic indexing + excludeDirs.add(file("${edenDir}/.cache")) } } diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 13007f10e4..c642dbdcda 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ SPDX-License-Identifier: GPL-3.0-or-later { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/web/WebBrowser.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/web/WebBrowser.kt new file mode 100644 index 0000000000..898c88f4ac --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/web/WebBrowser.kt @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.applets.web + +import android.content.Intent +import android.net.Uri +import androidx.annotation.Keep +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.utils.Log + +/** + Should run WebBrowser as a new intent. +*/ + +@Keep +object WebBrowser { + @JvmStatic + fun openExternal(url: String) { + val activity = NativeLibrary.sEmulationActivity.get() ?: run { + return + } + + activity.runOnUiThread { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + activity.startActivity(intent) + } catch (e: Exception) { + Log.error("WebBrowser failed to launch $url: ${e.message}") + } + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt new file mode 100644 index 0000000000..11185f019e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.dialogs + +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.RadioGroup +import android.widget.TextView +import androidx.drawerlayout.widget.DrawerLayout +import com.google.android.material.color.MaterialColors +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.IntSetting +import org.yuzu.yuzu_emu.fragments.EmulationFragment +import org.yuzu.yuzu_emu.utils.NativeConfig +import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting + +class QuickSettings(val emulationFragment: EmulationFragment) { + // Kinda a crappy workaround to get a title from setting keys + // Idk how to do this witthout hardcoding every single one + private fun getSettingTitle(settingKey: String): String { + return settingKey.replace("_", " ").split(" ") + .joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } } + + } + + private fun saveSettings() { + if (emulationFragment.shouldUseCustom) { + NativeConfig.savePerGameConfig() + } else { + NativeConfig.saveGlobalConfig() + } + } + + fun addPerGameConfigStatusIndicator(container: ViewGroup) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val statusView = inflater.inflate(R.layout.item_quick_settings_status, container, false) + + val statusIcon = statusView.findViewById(R.id.status_icon) + val statusText = statusView.findViewById(R.id.status_text) + + statusIcon.setImageResource(R.drawable.ic_settings_outline) + statusText.text = emulationFragment.getString(R.string.using_per_game_config) + statusText.setTextColor( + MaterialColors.getColor( + statusText, + com.google.android.material.R.attr.colorPrimary + ) + ) + + container.addView(statusView) + } + + // settings + + fun addIntSetting( + container: ViewGroup, + setting: IntSetting, + namesArrayId: Int, + valuesArrayId: Int + ) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) + val headerView = itemView.findViewById(R.id.setting_header) + val titleView = itemView.findViewById(R.id.setting_title) + val valueView = itemView.findViewById(R.id.setting_value) + val expandIcon = itemView.findViewById(R.id.expand_icon) + val radioGroup = itemView.findViewById(R.id.radio_group) + + titleView.text = getSettingTitle(setting.key) + + val names = emulationFragment.resources.getStringArray(namesArrayId) + val values = emulationFragment.resources.getIntArray(valuesArrayId) + val currentIndex = values.indexOf(setting.getInt()) + + valueView.text = if (currentIndex >= 0) names[currentIndex] else "Null" + headerView.visibility = View.VISIBLE + + var isExpanded = false + names.forEachIndexed { index, name -> + val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(emulationFragment.requireContext()) + radioButton.text = name + radioButton.id = View.generateViewId() + radioButton.isChecked = index == currentIndex + radioButton.setPadding(16, 8, 16, 8) + + radioButton.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + setting.setInt(values[index]) + saveSettings() + valueView.text = name + } + } + radioGroup.addView(radioButton) + } + + headerView.setOnClickListener { + isExpanded = !isExpanded + if (isExpanded) { + radioGroup.visibility = View.VISIBLE + expandIcon.animate().rotation(180f).setDuration(200).start() + } else { + radioGroup.visibility = View.GONE + expandIcon.animate().rotation(0f).setDuration(200).start() + } + } + + container.addView(itemView) + } + + fun addBooleanSetting( + container: ViewGroup, + setting: BooleanSetting + ) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) + + val switchContainer = itemView.findViewById(R.id.switch_container) + val titleView = itemView.findViewById(R.id.switch_title) + val switchView = itemView.findViewById(R.id.setting_switch) + + titleView.text = getSettingTitle(setting.key) + switchContainer.visibility = View.VISIBLE + switchView.isChecked = setting.getBoolean() + + switchView.setOnCheckedChangeListener { _, isChecked -> + setting.setBoolean(isChecked) + saveSettings() + } + + switchContainer.setOnClickListener { + switchView.toggle() + } + container.addView(itemView) + } + + fun addSliderSetting( + container: ViewGroup, + setting: AbstractSetting, + minValue: Int = 0, + maxValue: Int = 100, + units: String = "" + ) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) + + val sliderContainer = itemView.findViewById(R.id.slider_container) + val titleView = itemView.findViewById(R.id.slider_title) + val valueDisplay = itemView.findViewById(R.id.slider_value_display) + val slider = itemView.findViewById(R.id.setting_slider) + + + titleView.text = getSettingTitle(setting.key) + sliderContainer.visibility = View.VISIBLE + + slider.valueFrom = minValue.toFloat() + slider.valueTo = maxValue.toFloat() + slider.stepSize = 1f + val currentValue = when (setting) { + is AbstractShortSetting -> setting.getShort(needsGlobal = false).toInt() + is AbstractIntSetting -> setting.getInt(needsGlobal = false) + else -> 0 + } + slider.value = currentValue.toFloat().coerceIn(minValue.toFloat(), maxValue.toFloat()) + + val displayValue = "${slider.value.toInt()}$units" + valueDisplay.text = displayValue + + slider.addOnChangeListener { _, value, chanhed -> + if (chanhed) { + val intValue = value.toInt() + when (setting) { + is AbstractShortSetting -> setting.setShort(intValue.toShort()) + is AbstractIntSetting -> setting.setInt(intValue) + } + saveSettings() + valueDisplay.text = "$intValue$units" + } + } + + slider.setOnTouchListener { _, event -> + val drawer = emulationFragment.view?.findViewById(R.id.drawer_layout) + when (event.action) { + MotionEvent.ACTION_DOWN -> { + drawer?.requestDisallowInterceptTouchEvent(true) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + drawer?.requestDisallowInterceptTouchEvent(false) + } + } + false + } + + container.addView(itemView) + } + + fun addDivider(container: ViewGroup) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false) + container.addView(dividerView) + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 688938d20d..40e75acff7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -16,6 +16,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_USE_SPEED_LIMIT("use_speed_limit"), USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"), SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), + FIX_BLOOM_EFFECTS("fix_bloom_effects"), CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"), USE_DOCKED_MODE("use_docked_mode"), USE_AUTO_STUB("use_auto_stub"), @@ -45,6 +46,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { AIRPLANE_MODE("airplane_mode"), SHOW_SOC_OVERLAY("show_soc_overlay"), + SHOW_BUILD_ID("show_build_id"), + SHOW_DRIVER_VERSION("show_driver_version"), SHOW_DEVICE_MODEL("show_device_model"), SHOW_GPU_MODEL("show_gpu_model"), SHOW_SOC_MODEL("show_soc_model"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 68a8a7a021..be3b2f4a48 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -15,7 +15,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { LANGUAGE_INDEX("language_index"), RENDERER_BACKEND("backend"), RENDERER_VRAM_USAGE_MODE("vram_usage_mode"), - RENDERER_SHADER_BACKEND("shader_backend"), RENDERER_NVDEC_EMULATION("nvdec_emulation"), RENDERER_ASTC_DECODE_METHOD("accelerate_astc"), RENDERER_ASTC_RECOMPRESSION("astc_recompression"), @@ -47,6 +46,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { FAST_CPU_TIME("fast_cpu_time"), CPU_TICKS("cpu_ticks"), FAST_GPU_TIME("fast_gpu_time"), + GPU_UNSWIZZLE_TEXTURE_SIZE("gpu_unswizzle_texture_size"), + GPU_UNSWIZZLE_STREAM_SIZE("gpu_unswizzle_stream_size"), + GPU_UNSWIZZLE_CHUNK_SIZE("gpu_unswizzle_chunk_size"), BAT_TEMPERATURE_UNIT("bat_temperature_unit"), CABINET_APPLET("cabinet_applet_mode"), CONTROLLER_APPLET("controller_applet_mode"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index b055d46d92..afa76362ff 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -311,15 +311,6 @@ abstract class SettingsItem( valuesId = R.array.rendererAccuracyValues ) ) - put( - SingleChoiceSetting( - IntSetting.RENDERER_SHADER_BACKEND, - titleId = R.string.shader_backend, - descriptionId = R.string.shader_backend_description, - choicesId = R.array.rendererShaderNames, - valuesId = R.array.rendererShaderValues - ) - ) put( SingleChoiceSetting( IntSetting.RENDERER_NVDEC_EMULATION, @@ -520,7 +511,20 @@ abstract class SettingsItem( valuesId = R.array.staticThemeValues ) ) - + put( + SwitchSetting( + BooleanSetting.SHOW_BUILD_ID, + titleId = R.string.show_build_id, + descriptionId = 0 + ) + ) + put( + SwitchSetting( + BooleanSetting.SHOW_DRIVER_VERSION, + titleId = R.string.show_driver_version, + descriptionId = 0 + ) + ) put( SwitchSetting( BooleanSetting.SHOW_DEVICE_MODEL, @@ -655,6 +659,33 @@ abstract class SettingsItem( valuesId = R.array.gpuValues ) ) + put( + SingleChoiceSetting( + IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE, + titleId = R.string.gpu_unswizzle_texture_size, + descriptionId = R.string.gpu_unswizzle_texture_size_description, + choicesId = R.array.gpuTextureSizeSwizzleEntries, + valuesId = R.array.gpuTextureSizeSwizzleValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.GPU_UNSWIZZLE_STREAM_SIZE, + titleId = R.string.gpu_unswizzle_stream_size, + descriptionId = R.string.gpu_unswizzle_stream_size_description, + choicesId = R.array.gpuSwizzleEntries, + valuesId = R.array.gpuSwizzleValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE, + titleId = R.string.gpu_unswizzle_chunk_size, + descriptionId = R.string.gpu_unswizzle_chunk_size_description, + choicesId = R.array.gpuSwizzleChunkEntries, + valuesId = R.array.gpuSwizzleChunkValues + ) + ) put( SingleChoiceSetting( IntSetting.FAST_CPU_TIME, @@ -688,6 +719,13 @@ abstract class SettingsItem( descriptionId = R.string.skip_cpu_inner_invalidation_description ) ) + put( + SwitchSetting( + BooleanSetting.FIX_BLOOM_EFFECTS, + titleId = R.string.fix_bloom_effects, + descriptionId = R.string.fix_bloom_effects_description + ) + ) put( SwitchSetting( BooleanSetting.CPUOPT_UNSAFE_HOST_MMU, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 262abc584e..b6cb7acf1e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.settings.ui @@ -242,9 +242,11 @@ class SettingsFragmentPresenter( add(BooleanSetting.USE_CUSTOM_CPU_TICKS.key) add(IntSetting.CPU_TICKS.key) - add(HeaderSetting(R.string.network)) - add(StringSetting.WEB_TOKEN.key) - add(StringSetting.WEB_USERNAME.key) + if (!NativeConfig.isPerGameConfigLoaded()) { + add(HeaderSetting(R.string.network)) + add(StringSetting.WEB_TOKEN.key) + add(StringSetting.WEB_USERNAME.key) + } } } @@ -255,7 +257,6 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_VSYNC.key) - add(IntSetting.RENDERER_SHADER_BACKEND.key) add(IntSetting.RENDERER_SCALING_FILTER.key) add(IntSetting.FSR_SHARPENING_SLIDER.key) add(IntSetting.RENDERER_ANTI_ALIASING.key) @@ -279,7 +280,11 @@ class SettingsFragmentPresenter( add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) + add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) + add(IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.key) + add(IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.key) + add(IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.key) add(HeaderSetting(R.string.extensions)) @@ -346,6 +351,8 @@ class SettingsFragmentPresenter( add(IntSetting.SOC_OVERLAY_POSITION.key) add(HeaderSetting(R.string.stats_overlay_items)) + add(BooleanSetting.SHOW_BUILD_ID.key) + add(BooleanSetting.SHOW_DRIVER_VERSION.key) add(BooleanSetting.SHOW_DEVICE_MODEL.key) add(BooleanSetting.SHOW_GPU_MODEL.key) @@ -1204,9 +1211,11 @@ class SettingsFragmentPresenter( add(SettingsItem.FASTMEM_COMBINED) add(BooleanSetting.CPUOPT_UNSAFE_HOST_MMU.key) - add(HeaderSetting(R.string.log)) + if (!NativeConfig.isPerGameConfigLoaded()) { + add(HeaderSetting(R.string.log)) - add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key) + add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key) + } add(HeaderSetting(R.string.general)) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt index 130b556c1f..525fbd9f91 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -69,7 +69,8 @@ class DriverFetcherFragment : Fragment() { DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0), DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1), DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true, SortMode.PublishTime), - DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3) + DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3), + DriverRepo("Whitebelyash Turnip", "whitebelyash/freedreno_turnip-CI", sort=4, false, SortMode.PublishTime), ) private val driverMap = listOf( @@ -79,7 +80,7 @@ class DriverFetcherFragment : Fragment() { IntRange(600, 639) to "Mr. Purple EOL-24.3.4", IntRange(640, 699) to "Mr. Purple T19", IntRange(700, 710) to "KIMCHI 25.2.0_r5", - IntRange(711, 799) to "Mr. Purple T22", + IntRange(711, 799) to "Mr. Purple T23", IntRange(800, 899) to "GameHub Adreno 8xx", IntRange(900, Int.MAX_VALUE) to "Unsupported" ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 1bac1bd1ed..97f0dfa86a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -68,12 +68,14 @@ import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding +import org.yuzu.yuzu_emu.dialogs.QuickSettings import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationVerticalAlignment +import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel @@ -96,6 +98,7 @@ import java.io.ByteArrayOutputStream import java.io.File import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlin.or class EmulationFragment : Fragment(), SurfaceHolder.Callback { private lateinit var emulationState: EmulationState @@ -105,7 +108,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private var socUpdater: (() -> Unit)? = null val handler = Handler(Looper.getMainLooper()) - private var isOverlayVisible = true + private var controllerInputReceived = false private var _binding: FragmentEmulationBinding? = null @@ -124,6 +127,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private lateinit var gpuModel: String private lateinit var fwVersion: String + private lateinit var buildId: String + private lateinit var driverInUse: String private var intentGame: Game? = null private var isCustomSettingsIntent = false @@ -133,6 +138,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private var isAmiiboPickerOpen = false private var amiiboLoadJob: Job? = null + private var wasInputOverlayAutoHidden = false + private var overlayTouchActive = false + + var shouldUseCustom = false + private var isQuickSettingsMenuOpen = false + private val quickSettings = QuickSettings(this) + private val loadAmiiboLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> isAmiiboPickerOpen = false @@ -280,7 +292,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // Normal game launch from arguments else -> { - val shouldUseCustom = game?.let { it == args.game && args.custom } ?: false + shouldUseCustom = game?.let { it == args.game && args.custom } ?: false if (shouldUseCustom) { SettingsFile.loadCustomConfig(game!!) @@ -632,6 +644,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { gpuModel = GpuDriverHelper.hookLibPath?.let { GpuDriverHelper.getGpuModel(hookLibPath = it).toString() } ?: "Unknown" fwVersion = NativeLibrary.firmwareVersion() + val buildVersion = NativeLibrary.getBuildVersion() + buildId = buildVersion.split("-").getOrNull(0) ?: "" + driverInUse = driverViewModel.selectedDriverVersion.value + updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) binding.surfaceEmulation.holder.addCallback(this) @@ -656,6 +672,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.inGameMenu.requestFocus() emulationViewModel.setDrawerOpen(true) updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) + if (drawerView == binding.inGameMenu) { + binding.drawerLayout.closeDrawer(binding.quickSettingsSheet) + } else if (drawerView == binding.quickSettingsSheet) { + binding.drawerLayout.closeDrawer(binding.inGameMenu) + } } override fun onDrawerClosed(drawerView: View) { @@ -712,11 +733,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { updateQuickOverlayMenuEntry(newState) binding.surfaceInputOverlay.refreshControls() // Sync view visibility with the setting - if (newState) { - showOverlay() - } else { - hideOverlay() - } + toggleOverlay(newState) NativeConfig.saveGlobalConfig() true } @@ -727,16 +744,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { Settings.MenuTag.SECTION_ROOT ) binding.inGameMenu.requestFocus() + binding.drawerLayout.closeDrawer(binding.quickSettingsSheet) binding.root.findNavController().navigate(action) true } + R.id.menu_quick_settings -> { + openQuickSettingsMenu() + true + } + R.id.menu_settings_per_game -> { val action = HomeNavigationDirections.actionGlobalSettingsActivity( args.game, Settings.MenuTag.SECTION_ROOT ) binding.inGameMenu.requestFocus() + binding.drawerLayout.closeDrawer(binding.quickSettingsSheet) binding.root.findNavController().navigate(action) true } @@ -802,6 +826,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + addQuickSettings() + + binding.drawerLayout.addDrawerListener(object : DrawerListener { + override fun onDrawerSlide(drawerView: View, slideOffset: Float) { + // no op + } + + override fun onDrawerOpened(drawerView: View) { + if (drawerView == binding.quickSettingsSheet) { + isQuickSettingsMenuOpen = true + if (shouldUseCustom) { + SettingsFile.loadCustomConfig(args.game!!) + } + } + } + + override fun onDrawerClosed(drawerView: View) { + if (drawerView == binding.quickSettingsSheet) { + isQuickSettingsMenuOpen = false + if (shouldUseCustom) { + NativeConfig.unloadPerGameConfig() + } + } + } + + override fun onDrawerStateChanged(newState: Int) { + // No op + } + }) + setInsets() requireActivity().onBackPressedDispatcher.addCallback( @@ -980,6 +1034,73 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun addQuickSettings() { + binding.quickSettingsSheet.apply { + val container = binding.quickSettingsSheet.findViewById(R.id.quick_settings_container) + + container.removeAllViews() + + if (shouldUseCustom) { + quickSettings.addPerGameConfigStatusIndicator(container) + } + + quickSettings.addBooleanSetting( + container, + BooleanSetting.RENDERER_USE_SPEED_LIMIT, + ) + + quickSettings.addSliderSetting( + container, + ShortSetting.RENDERER_SPEED_LIMIT, + minValue = 0, + maxValue = 400, + units = "%", + ) + + quickSettings.addBooleanSetting( + container, + BooleanSetting.USE_DOCKED_MODE, + ) + + quickSettings.addDivider(container) + + quickSettings.addIntSetting( + container, + IntSetting.RENDERER_ACCURACY, + R.array.rendererAccuracyNames, + R.array.rendererAccuracyValues + ) + + + quickSettings.addIntSetting( + container, + IntSetting.RENDERER_SCALING_FILTER, + R.array.rendererScalingFilterNames, + R.array.rendererScalingFilterValues + ) + + quickSettings.addSliderSetting( + container, + IntSetting.FSR_SHARPENING_SLIDER, + minValue = 0, + maxValue = 100, + units = "%" + ) + + quickSettings.addIntSetting( + container, + IntSetting.RENDERER_ANTI_ALIASING, + R.array.rendererAntiAliasingNames, + R.array.rendererAntiAliasingValues + ) + } + } + + private fun openQuickSettingsMenu() { + binding.drawerLayout.closeDrawer(binding.inGameMenu) + binding.drawerLayout.openDrawer(binding.quickSettingsSheet) + } + private fun updateQuickOverlayMenuEntry(isVisible: Boolean) { val b = _binding ?: return val item = b.inGameMenu.menu.findItem(R.id.menu_quick_overlay) ?: return @@ -1125,6 +1246,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { super.onDestroyView() amiiboLoadJob?.cancel() amiiboLoadJob = null + _binding?.surfaceInputOverlay?.touchEventListener = null _binding = null isAmiiboPickerOpen = false } @@ -1151,6 +1273,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // we need to reinitialize the auto-hide timer initializeOverlayAutoHide() + addQuickSettings() } private fun resetInputOverlay() { @@ -1164,7 +1287,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { @SuppressLint("DefaultLocale") private fun updateShowStatsOverlay() { - val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() + val showPerfOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() binding.showStatsOverlayText.apply { setTextColor( MaterialColors.getColor( @@ -1173,12 +1296,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { ) ) } - binding.showStatsOverlayText.setVisible(showOverlay) - if (showOverlay) { - val SYSTEM_FPS = 0 + binding.showStatsOverlayText.setVisible(showPerfOverlay) + if (showPerfOverlay) { + //val SYSTEM_FPS = 0 val FPS = 1 val FRAMETIME = 2 - val SPEED = 3 + //val SPEED = 3 val sb = StringBuilder() perfStatsUpdater = { if (emulationViewModel.emulationStarted.value && @@ -1351,7 +1474,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } private fun updateSocOverlay() { - val showOverlay = BooleanSetting.SHOW_SOC_OVERLAY.getBoolean() + val showSOCOverlay = BooleanSetting.SHOW_SOC_OVERLAY.getBoolean() binding.showSocOverlayText.apply { setTextColor( MaterialColors.getColor( @@ -1360,30 +1483,48 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { ) ) } - binding.showSocOverlayText.setVisible(showOverlay) + binding.showSocOverlayText.setVisible(showSOCOverlay) - if (showOverlay) { + if (showSOCOverlay) { val sb = StringBuilder() - + val appendWithPipe: (String) -> Unit = { text -> + if (text.isNotEmpty()) { + if (sb.isNotEmpty()) sb.append(" | ") + sb.append(text) + } + } socUpdater = { if (emulationViewModel.emulationStarted.value && !emulationViewModel.isEmulationStopping.value ) { sb.setLength(0) + if (BooleanSetting.SHOW_BUILD_ID.getBoolean( + NativeConfig.isPerGameConfigLoaded() + ) + ) { + appendWithPipe(buildId) + } + + if (BooleanSetting.SHOW_DRIVER_VERSION.getBoolean( + NativeConfig.isPerGameConfigLoaded() + ) + ) { + appendWithPipe(driverInUse) + } + if (BooleanSetting.SHOW_DEVICE_MODEL.getBoolean( NativeConfig.isPerGameConfigLoaded() ) ) { - sb.append(Build.MODEL) + appendWithPipe(Build.MODEL) } if (BooleanSetting.SHOW_GPU_MODEL.getBoolean( NativeConfig.isPerGameConfigLoaded() ) ) { - if (sb.isNotEmpty()) sb.append(" | ") - sb.append(gpuModel) + appendWithPipe(gpuModel) } if (Build.VERSION.SDK_INT >= 31) { @@ -1391,8 +1532,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { NativeConfig.isPerGameConfigLoaded() ) ) { - if (sb.isNotEmpty()) sb.append(" | ") - sb.append(Build.SOC_MODEL) + appendWithPipe(Build.SOC_MODEL) } } @@ -1400,8 +1540,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { NativeConfig.isPerGameConfigLoaded() ) ) { - if (sb.isNotEmpty()) sb.append(" | ") - sb.append(fwVersion) + appendWithPipe(fwVersion) } binding.showSocOverlayText.text = sb.toString() @@ -1674,9 +1813,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.id.menu_show_overlay -> { it.isChecked = !it.isChecked - BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked) - updateQuickOverlayMenuEntry(it.isChecked) - binding.surfaceInputOverlay.refreshControls() + toggleOverlay(it.isChecked) true } @@ -1811,6 +1948,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { windowInsets } + + ViewCompat.setOnApplyWindowInsetsListener(binding.quickSettingsSheet) { v, insets -> + val systemBarsInsets: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + if (v.layoutDirection == View.LAYOUT_DIRECTION_LTR) { + v.setPadding( + systemBarsInsets.left, + systemBarsInsets.top, + 0, + systemBarsInsets.bottom + ) + } else { + v.setPadding( + 0, + systemBarsInsets.top, + systemBarsInsets.right, + systemBarsInsets.bottom + ) + } + insets + } } private class EmulationState( @@ -2000,68 +2157,87 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private fun startOverlayAutoHideTimer(seconds: Int) { handler.removeCallbacksAndMessages(null) + val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() handler.postDelayed({ - if (isOverlayVisible && isAdded && _binding != null) { - hideOverlay() + if (showInputOverlay && isAdded && _binding != null) { + if (overlayTouchActive) { + startOverlayAutoHideTimer(seconds) + } else { + autoHideOverlay() + } } }, seconds * 1000L) } fun handleScreenTap(isLongTap: Boolean) { - if (binding.surfaceInputOverlay.isGamelessMode()) { - return - } - - val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt() - val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() - - if (!shouldProceed) { - return - } - + if (!isAdded || _binding == null) return + if (binding.surfaceInputOverlay.isGamelessMode()) return + if (!BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()) return // failsafe - if (autoHideSeconds == 0) { - showOverlay() - return - } - - if (!isOverlayVisible && !isLongTap) { - showOverlay() - } - - startOverlayAutoHideTimer(autoHideSeconds) - } - - private fun initializeOverlayAutoHide() { - if (binding.surfaceInputOverlay.isGamelessMode()) { - return - } - val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt() - val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() - val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() - - if (autoHideEnabled && showOverlay) { - showOverlay() + if (autoHideSeconds == 0) { + toggleOverlay(true) + } else { + val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() + if (!showInputOverlay && !isLongTap && wasInputOverlayAutoHidden) { + toggleOverlay(true) + } startOverlayAutoHideTimer(autoHideSeconds) } } + private fun initializeOverlayAutoHide() { + if (!isAdded || _binding == null) return + if (binding.surfaceInputOverlay.isGamelessMode()) return - fun showOverlay() { - if (!isOverlayVisible) { - isOverlayVisible = true - // Reset controller input flag so controller can hide overlay again - controllerInputReceived = false - ViewUtils.showView(binding.surfaceInputOverlay, 500) + val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt() + val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() + val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() + if (autoHideEnabled && showInputOverlay) { + toggleOverlay(true) + startOverlayAutoHideTimer(autoHideSeconds) + } + + binding.surfaceInputOverlay.touchEventListener = { event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { + overlayTouchActive = true + handler.removeCallbacksAndMessages(null) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { + overlayTouchActive = event.pointerCount > 1 + if (!overlayTouchActive) handleScreenTap(isLongTap = false) + } + MotionEvent.ACTION_CANCEL -> { + overlayTouchActive = false + handleScreenTap(isLongTap = false) + } + MotionEvent.ACTION_MOVE -> { + overlayTouchActive = true + } + } } } - private fun hideOverlay() { - if (isOverlayVisible) { - isOverlayVisible = false - ViewUtils.hideView(binding.surfaceInputOverlay) + private fun autoHideOverlay() { + toggleOverlay(false) + wasInputOverlayAutoHidden = true + } + + fun toggleOverlay(enable: Boolean) { + if (!isAdded || _binding == null) return + if (enable == !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) { + // Reset controller input flag so controller can hide overlay again + if (!enable) { + controllerInputReceived = false + } + if (enable) { + wasInputOverlayAutoHidden = false + } + BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(enable) + updateQuickOverlayMenuEntry(enable) + binding.surfaceInputOverlay.refreshControls() } } @@ -2070,7 +2246,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return if (controllerInputReceived) return controllerInputReceived = true - hideOverlay() + autoHideOverlay() } fun onControllerConnected() { @@ -2081,6 +2257,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return controllerInputReceived = false - showOverlay() + toggleOverlay(true) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index d26320bbb3..cd5792b33a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -1,9 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - package org.yuzu.yuzu_emu.model import androidx.lifecycle.ViewModel @@ -24,6 +21,7 @@ import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver import org.yuzu.yuzu_emu.utils.GpuDriverHelper +import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.utils.GpuDriverMetadata import org.yuzu.yuzu_emu.utils.NativeConfig import java.io.File @@ -51,6 +49,9 @@ class DriverViewModel : ViewModel() { private val _selectedDriverTitle = MutableStateFlow("") val selectedDriverTitle: StateFlow get() = _selectedDriverTitle + private val _selectedDriverVersion = MutableStateFlow("") + val selectedDriverVersion: StateFlow get() = _selectedDriverVersion + private val _showClearButton = MutableStateFlow(false) val showClearButton = _showClearButton.asStateFlow() @@ -77,11 +78,13 @@ class DriverViewModel : ViewModel() { fun updateDriverList() { val selectedDriver = GpuDriverHelper.customDriverSettingData val systemDriverData = GpuDriverHelper.getSystemDriverInfo() + val systemDriverTitle = YuzuApplication.appContext.getString(R.string.system_gpu_driver) val newDriverList = mutableListOf( Driver( selectedDriver == GpuDriverMetadata(), - YuzuApplication.appContext.getString(R.string.system_gpu_driver), - systemDriverData?.get(0) ?: "", + systemDriverTitle, + //systemDriverData?.get(0) ?: "", + NativeLibrary.getVulkanDriverVersion().takeIf { !it.isNullOrEmpty() } ?: systemDriverTitle, systemDriverData?.get(1) ?: "" ) ) @@ -233,8 +236,15 @@ class DriverViewModel : ViewModel() { } private fun updateName() { - _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name - ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) + val systemDriverTitle = YuzuApplication.appContext.getString(R.string.system_gpu_driver) + //val systemDriverVersion = GpuDriverHelper.getSystemDriverInfo()?.get(0) ?: systemDriverTitle //title as fallback just in case + val systemDriverVersion = NativeLibrary.getVulkanDriverVersion().takeIf { !it.isNullOrEmpty() } ?: systemDriverTitle + val customDriver = GpuDriverHelper.customDriverSettingData + + _selectedDriverTitle.value = customDriver.name + ?: systemDriverTitle + _selectedDriverVersion.value = customDriver.version + ?: systemDriverVersion } private fun setDriverReady() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index d1252fc3c4..e18077c673 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -73,6 +73,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : var layout = OverlayLayout.Landscape + // External listener for EmulationFragment joypad overlay auto-hide + var touchEventListener: ((MotionEvent) -> Unit)? = null + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) @@ -138,6 +141,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : } override fun onTouch(v: View, event: MotionEvent): Boolean { + try { + touchEventListener?.invoke(event) + } catch (e: Exception) {} + if (inEditMode) { return onTouchWhileEditing(event) } diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index eb8e7b77ea..f8260e183a 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -160,7 +160,14 @@ namespace AndroidSettings { Settings::Setting show_soc_overlay{linkage, true, "show_soc_overlay", Settings::Category::Overlay, Settings::Specialization::Paired, true, true}; - + Settings::Setting show_build_id{linkage, true, "show_build_id", + Settings::Category::Overlay, + Settings::Specialization::Default, true, true, + &show_performance_overlay}; + Settings::Setting show_driver_version{linkage, true, "show_driver_version", + Settings::Category::Overlay, + Settings::Specialization::Default, true, true, + &show_performance_overlay}; Settings::Setting show_device_model{linkage, true, "show_device_model", Settings::Category::Overlay, Settings::Specialization::Default, true, true, diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index e656c2edad..5746659b68 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -66,6 +66,7 @@ #include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" +#include "common/android/applets/web_browser.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/filesystem/filesystem.h" @@ -275,6 +276,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string // Initialize system. jauto android_keyboard = std::make_unique(); + jauto android_webapplet = std::make_unique(); m_software_keyboard = android_keyboard.get(); m_system.SetShuttingDown(false); m_system.ApplySettings(); @@ -289,7 +291,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string nullptr, // Photo Viewer nullptr, // Profile Selector std::move(android_keyboard), // Software Keyboard - nullptr, // Web Browser + std::move(android_webapplet),// Web Browser nullptr, // Net Connect }); diff --git a/src/android/app/src/main/res/drawable/ic_launcher_foreground.png b/src/android/app/src/main/res/drawable/ic_launcher_foreground.png index b9adec4b1b..259f325987 100644 Binary files a/src/android/app/src/main/res/drawable/ic_launcher_foreground.png and b/src/android/app/src/main/res/drawable/ic_launcher_foreground.png differ diff --git a/src/android/app/src/main/res/drawable/ic_yuzu.png b/src/android/app/src/main/res/drawable/ic_yuzu.png index 2a4a061a06..1b580169a8 100644 Binary files a/src/android/app/src/main/res/drawable/ic_yuzu.png and b/src/android/app/src/main/res/drawable/ic_yuzu.png differ diff --git a/src/android/app/src/main/res/drawable/ic_yuzu_named.png b/src/android/app/src/main/res/drawable/ic_yuzu_named.png index 955b1e5114..bb09e0ae5f 100644 Binary files a/src/android/app/src/main/res/drawable/ic_yuzu_named.png and b/src/android/app/src/main/res/drawable/ic_yuzu_named.png differ diff --git a/src/android/app/src/main/res/drawable/ic_yuzu_splash.png b/src/android/app/src/main/res/drawable/ic_yuzu_splash.png index 2c83b2e751..53cffb98d9 100644 Binary files a/src/android/app/src/main/res/drawable/ic_yuzu_splash.png and b/src/android/app/src/main/res/drawable/ic_yuzu_splash.png differ diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index a5ebd2df3a..7f5f039d5e 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -171,14 +171,27 @@ + tools:visibility="gone"> + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_divider.xml b/src/android/app/src/main/res/layout/item_quick_settings_divider.xml new file mode 100644 index 0000000000..1c05181aa4 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_divider.xml @@ -0,0 +1,5 @@ + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_menu.xml b/src/android/app/src/main/res/layout/item_quick_settings_menu.xml new file mode 100644 index 0000000000..bcb3091037 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_menu.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_status.xml b/src/android/app/src/main/res/layout/item_quick_settings_status.xml new file mode 100644 index 0000000000..5b85bcd6f7 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_status.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/layout_quick_settings.xml b/src/android/app/src/main/res/layout/layout_quick_settings.xml new file mode 100644 index 0000000000..0e6f75d76e --- /dev/null +++ b/src/android/app/src/main/res/layout/layout_quick_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml index 70fb48c13b..c1d8147f84 100644 --- a/src/android/app/src/main/res/menu/menu_in_game.xml +++ b/src/android/app/src/main/res/menu/menu_in_game.xml @@ -18,6 +18,11 @@ android:icon="@drawable/ic_settings" android:title="@string/preferences_settings" /> + + طبقة الجهاز ضبط المعلومات التي يتم عرضها في طبقة الجهاز + عرض معرف البناء + عرض إصدار برنامج التشغيل عرض طراز الجهاز عرض طراز وحدة معالجة الرسومات عرض طراز الرقائق @@ -100,11 +102,6 @@ للإستخدام في التطوير فقط. 0 إلى 65535 - - GLSL - GLASM - SPIRV - محاكاة NVDEC حدد كيفية التعامل مع فك تشفير الفيديو (NVDEC) خلال المشاهد التمهيدية والمقدمة. @@ -458,8 +455,6 @@ الدقة (الإرساء/محمول) VSync وضع - واجهة برمجة التظليل - حدد كيفية تجميع وترجمة برامج التظليل لوحدة معالجة الرسومات الخاصة بك. مرشح ملائم للنافذة حدة FSR يحدد مدى وضوح الصورة عند استخدام التباين الديناميكي لـ FSR @@ -1013,10 +1008,6 @@ 16:10 فرض تمديد إلى النافذة - - Dynarmic (JIT) - تنفيذ التعليمات البرمجية الأصلية (NCE) - دقيق غير آمن diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index 10f034144a..81cafb6b17 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -47,6 +47,8 @@ ئامێر ڕێکخستنی زانیارییەکانی نیشاندراو لە ئامێرەکە + نیشاندانی IDی بینا + نیشاندانی وەشانی درایڤەر نیشاندانی مۆدێلی ئامێر نیشاندانی مۆدێلی GPU نیشاندانی مۆدێلی SoC @@ -63,9 +65,7 @@ چالاککردنی میمیکردنی MMU میواندە ئەم باشکردنە خێرایی دەستکەوتنی بیرگە لەلایەن پرۆگرامی میوانەکە زیاد دەکات. چالاککردنی وای لێدەکات کە خوێندنەوە/نووسینەکانی بیرگەی میوانەکە ڕاستەوخۆ لە بیرگە ئەنجام بدرێت و میمیکردنی MMU میواندە بەکاربهێنێت. ناچالاککردنی ئەمە هەموو دەستکەوتنەکانی بیرگە ڕەت دەکاتەوە لە بەکارهێنانی میمیکردنی MMU نەرمەکاڵا. - - GLSL - GLASM + ئیمولەیشنی NVDEC هەڵبژاردنی ڕێگای دیکۆدکردنی ڤیدیۆ @@ -339,8 +339,6 @@ ڕوونی (دۆخی دەستی/دۆخی دۆک) دۆخی VSync - شادەر باکند - هەڵبژاردنی ڕێگای پێکهێنانی شادەر فلتەری گونجاندنی پەنجەرە تیژی FSR دیاریکردنی تیژی وێنە لە کاتی بەکارهێنانی FSR diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index c0f65b9c61..3ea8647d1d 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -47,6 +47,8 @@ Překryv zařízení Konfigurovat, jaké informace se zobrazí v překryvu zařízení + Zobrazit ID sestavení + Zobrazit verzi ovladače Zobrazit model zařízení Zobrazit model GPU Zobrazit model SoC @@ -63,9 +65,7 @@ Povolit emulaci hostitelské MMU Tato optimalizace zrychluje přístup do paměti hostovaného programu. Její povolení způsobí, že čtení a zápisy do paměti hosta se provádějí přímo v paměti a využívají hostitelskou MMU. Zakázání této funkce vynutí použití softwarové emulace MMU pro všechny přístupy do paměti. - - GLSL - GLASM + Emulace NVDEC Zpracování dekódování videa @@ -327,8 +327,6 @@ Rozlišení (Handheld/Docked) VSync režim - Backend shaderů - Způsob kompilace shaderů Filtr přizpůsobení Ostrost FSR Nastavení ostrosti pro FSR diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 0909f0dee1..a8f696bfe8 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -55,6 +55,8 @@ Geräte-Overlay Konfigurieren Sie, welche Informationen im Geräte-Overlay angezeigt werden + Build-ID anzeigen + Treiberversion anzeigen Gerätemodell anzeigen GPU-Modell anzeigen SoC-Modell anzeigen @@ -71,9 +73,7 @@ Host-MMU-Emulation aktivieren Diese Optimierung beschleunigt Speicherzugriffe durch das Gastprogramm. Wenn aktiviert, erfolgen Speicherlese- und -schreibvorgänge des Gastes direkt im Speicher und nutzen die MMU des Hosts. Das Deaktivieren erzwingt die Verwendung der Software-MMU-Emulation für alle Speicherzugriffe. - - GLSL - GLASM + NVDEC-Emulation Methode zur Videodekodierung @@ -383,8 +383,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Auflösung (Handheld/Gedockt) VSync-Modus - Shader-Backend - Methode zur Shader-Kompilierung Skalierungsfilter FSR-Schärfe Bestimmt die Schärfe bei FSR-Nutzung. @@ -831,10 +829,6 @@ Wirklich fortfahren? 16:10 erzwingen Auf Bildschirmgröße anpsassen - - Dynarmic (JIT) - Ausführung nativen Codes (ACE) - Akkurat Unsicher diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 80b386e5e0..56be857430 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -65,6 +65,8 @@ Superposición del dispositivo Configurar qué información se muestra en la superposición del dispositivo + Mostrar ID de compilación + Mostrar versión del controlador Mostrar modelo del dispositivo Mostrar modelo de la GPU Mostrar modelo del SoC @@ -81,9 +83,7 @@ Habilitar emulación de MMU del anfitrión Esta optimización acelera el acceso a la memoria del programa invitado. Al habilitarla, las lecturas y escrituras de la memoria del invitado se realizan directamente en la memoria y utilizan la MMU del anfitrión. Al deshabilitarla, todos los accesos a la memoria utilizan el software de emulación de la MMU. - - GLSL - GLASM + Emulación NVDEC Seleccione cómo se maneja la decodificación de vídeo (NVDEC) durante las escenas y las introducciones. @@ -398,8 +398,6 @@ Resolución (Portátil/Sobremesa) Modo VSync - Backend de sombreador - Elija cómo se compilan y traducen los sombreadores para su GPU. Filtro de adaptación de ventana Nitidez FSR Ajusta la intensidad del filtro de enfoque al usar el contraste dinámico de FSR. diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index e15f3c3851..3f123e872a 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -48,6 +48,8 @@ نمایش اطلاعات دستگاه تنظیم اطلاعات نمایش داده شده در نمایشگر دستگاه + نمایش ID بیلد + نمایش نسخه درایور نمایش مدل دستگاه نمایش مدل GPU نمایش مدل SoC @@ -127,13 +129,6 @@ محافظه‌کارانه تهاجمی - - بک‌اند شیدر - انتخاب روش کامپایل و ترجمه شیدرها - GLSL - GLASM - Spir-V - شبیه‌سازی NVDEC انتخاب روش پردازش ویدیو (NVDEC) diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 4554a36499..f138108d53 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -65,6 +65,8 @@ Overlay de l\'appareil Configurer les informations affichées dans l\'overlay de l\'appareil + Afficher l\'ID de build + Afficher la version du pilote Afficher le modèle de l\'appareil Afficher le modèle du GPU Afficher le modèle du SoC @@ -85,9 +87,6 @@ À usage de développement uniquement. 0 à 65535 - - GLSL - GLASM Émulation NVDEC Sélectionnez la manière dont le décodage vidéo (NVDEC) est géré pendant les cinématiques et les intros. @@ -412,8 +411,6 @@ Résolution (Mode Portable/Mode TV) Mode VSync - Backend shader - Méthode de compilation Filtre de fenêtre adaptatif Netteté FSR Détermine à quel point l\'image sera affinée lors de l\'utilisation du contraste dynamique FSR. diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index d9fad35484..241ee7a8fb 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -47,6 +47,8 @@ הצגת מידע על המכשיר הגדר אילו פרטים יוצגו בהצגת המידע על המכשיר + הצג מזהה Build + הצג גרסת דרייבר הצג דגם מכשיר הצג דגם GPU הצג דגם SoC @@ -63,9 +65,7 @@ הפעל אמולציית MMU מארח אופטימיזציה זו מאיצה את גישת הזיכרון על ידי התוכנית האורחת. הפעלתה גורמת לכך שפעולות קריאה/כתיבה לזיכרון האורח מתבצעות ישירות לזיכרון ומשתמשות ב-MMU של המארח. השבתת זאת מאלצת את כל גישות הזיכרון להשתמש באמולציית MMU תוכנתית. - - GLSL - GLASM + אמולציית NVDEC בחר כיצד לטפל בפענוח וידאו @@ -363,8 +363,6 @@ רזולוציה (מעוגן/נייד) מצב VSync - מנוע שיידרים - בחר כיצד לקמפל שיידרים פילטר מתאם חלון חדות FSR קובע את מידת החדות בעת שימוש ב-FSR. diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index 1c288d5f81..d5273c26f2 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -47,6 +47,8 @@ Eszköz információk Állítsd be, milyen információk jelenjenek meg az eszköz információiban + Build azonosító megjelenítése + Illesztőprogram-verzió megjelenítése Eszköz modell megjelenítése GPU modell megjelenítése SoC modell megjelenítése @@ -63,9 +65,7 @@ Gazda MMU emuláció engedélyezése Ez az optimalizáció gyorsítja a vendégprogram memória-hozzáférését. Engedélyezése esetén a vendég memóriaolvasási/írási műveletei közvetlenül a memóriában történnek, és kihasználják a gazda MMU-ját. Letiltás esetén minden memória-hozzáférés a szoftveres MMU emulációt használja. - - GLSL - GLASM + NVDEC emuláció Videódekódolás kezelése @@ -358,8 +358,6 @@ Felbontás (Kézi/Dockolt) VSync mód - Shader backend - Shaderek fordításának módja Ablakhoz alkalmazkodó szűrő FSR élesség Meghatározza, milyen éles lesz a kép az FSR dinamikus kontraszt használata közben. diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index c039679527..2b53f23ed2 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -65,6 +65,8 @@ Overlay Perangkat Konfigurasi informasi yang ditampilkan di overlay perangkat + Tampilkan ID Build + Tampilkan Versi Driver Tampilkan Model Perangkat Tampilkan Model GPU Tampilkan Model SoC @@ -81,9 +83,7 @@ Aktifkan Emulasi MMU Host Optimasi ini mempercepat akses memori oleh program tamu. Mengaktifkannya menyebabkan pembacaan/penulisan memori tamu dilakukan langsung ke memori dan memanfaatkan MMU Host. Menonaktifkan ini memaksa semua akses memori menggunakan Emulasi MMU Perangkat Lunak. - - GLSL - GLASM + Emulasi NVDEC Pilih cara decoding video (NVDEC) ditangani selama cutscene dan intro. @@ -390,8 +390,6 @@ Resolusi (Handheld/Docked) Mode Sinkronisasi Vertikal - Backend Shader - Pilih cara shader dikompilasi dan diterjemahkan untuk GPU Anda. Filter penyesuaian jendela Ketajaman FSR Menentukan seberapa tajam gambar akan terlihat saat menggunakan kontras dinamis FSR diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index 29a4ec0e1e..8ec2b2cebd 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -65,6 +65,8 @@ Overlay dispositivo Configura quali informazioni mostrare nell\'overlay del dispositivo + Mostra ID build + Mostra versione driver Mostra modello dispositivo Mostra modello GPU Mostra modello SoC @@ -81,9 +83,7 @@ Abilita l\'emulazione della MMU nell\'host Questa ottimizzazione accelera gli accessi alla memoria da parte del programma guest. Abilitandola, le letture/scritture della memoria guest vengono eseguite direttamente in memoria e sfruttano la MMU host. Disabilitandola, tutti gli accessi alla memoria sono costretti a utilizzare l\'emulazione software della MMU. - - GLSL - GLASM + Emulazione NVDEC Scegli come gestire la decodifica video @@ -399,8 +399,6 @@ Risoluzione (Portatile/Docked) Modalità VSync - Backend shader - Scegli come compilare gli shader Filtro adattivo della finestra Nitidezza FSR Determina quanto sarà nitida l\'immagine utilizzando il contrasto dinamico di FSR diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 9c1249a614..4676b8e975 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -47,6 +47,8 @@ デバイスオーバーレイ デバイスオーバーレイに表示される情報を設定 + ビルドIDを表示 + ドライバーのバージョンを表示 デバイスモデルを表示 GPUモデルを表示 SoCモデルを表示 @@ -63,9 +65,7 @@ ホストMMUエミュレーションを有効化 この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。有効にすると、ゲストのメモリ読み書きが直接メモリ内で実行され、ホストのMMUを利用します。無効にすると、すべてのメモリアクセスでソフトウェアMMUエミュレーションが使用されます。 - - GLSL - GLASM + NVDECエミュレーション ビデオデコード方法 @@ -358,8 +358,6 @@ 解像度(携帯モード/TVモード) 垂直同期モード - シェーダーバックエンド - シェーダーのコンパイル方法 ウィンドウ適応フィルター アンチエイリアス方式 コンパイル済みシェーダーを最適化し、GPUの効率を向上させます。 diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 60889b8f61..d807b9679d 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -47,6 +47,8 @@ 장치 오버레이 장치 오버레이에 표시할 정보 구성 + 빌드 ID 표시 + 드라이버 버전 표시 장치 모델 표시 GPU 모델 표시 SoC 모델 표시 @@ -63,9 +65,7 @@ 호스트 MMU 에뮬레이션 사용 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. 활성화하면 게스트의 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 활용합니다. 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하게 됩니다. - - GLSL - GLASM + NVDEC 에뮬레이션 비디오 디코딩 처리 방식 선택 @@ -358,8 +358,6 @@ 해상도 (휴대 모드/독 모드) 수직동기화 모드 - 셰이더 백엔드 - 셰이더 컴파일 방식 선택 윈도우 적응 필터 안티에일리어싱 방법 컴파일된 셰이더를 최적화하여 GPU 효율성을 향상시킵니다. diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 17e8088941..3d5933b2ea 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -47,6 +47,8 @@ Enhetsoverlegg Konfigurer hvilken informasjon som vises i enhetsoverlegget + Vis Build-ID + Vis driver-versjon Vis enhetsmodell Vis GPU-modell Vis SoC-modell @@ -63,9 +65,7 @@ Aktiver verts-MMU-emulering Denne optimaliseringen fremskynder minnetilgang av gjesteprogrammet. Hvis aktivert, utføres gjestens minnelesing/skriving direkte i minnet og bruker vertens MMU. Deaktivering tvinger alle minnetilganger til å bruke programvarebasert MMU-emulering. - - GLSL - GLASM + NVDEC-emulering Velg hvordan videodekoding håndteres @@ -339,8 +339,6 @@ Oppløsning (håndholdt/dokket) VSync-modus - Shader-backend - Velg hvordan shadere kompileres Filter for vindustilpasning FSR-skarphet Bestemmer bildekvalitet med FSR diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 4639fa04c9..61eb47f271 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -68,6 +68,8 @@ Nakładka urządzenia Skonfiguruj, jakie informacje są wyświetlane w nakładce urządzenia + Pokaż ID kompilacji + Pokaż wersję sterownika Pokaż model urządzenia Pokaż model GPU Pokaż model SoC @@ -88,9 +90,6 @@ Wyłącznie do użytku deweloperskiego. 0 do 65535 - - GLSL - GLASM Emulacja NVDEC Wybierz metodę dekodowania wideo (NVDEC). @@ -437,8 +436,6 @@ Rozdzielczość (Handheld/Zadokowany) Synchronizacja pionowa VSync - Backend shaderów - Wybierz metodę kompilacji shaderów. Filtr adaptacji rozdzielczości Ostrość FSR Kontroluje ostrość obrazu w FSR. diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index c536f740e3..696f7eb646 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -68,6 +68,8 @@ Sobreposição do Dispositivo Configurar quais informações são mostradas na sobreposição do dispositivo + Mostrar ID da Build + Mostrar Versão do Driver Mostrar Modelo do Dispositivo Mostrar Modelo da GPU Mostrar Modelo do SoC @@ -88,9 +90,6 @@ Apenas para uso em desenvolvimento. 0 a 65535 - - GLSL - GLASM Decodificação de Vídeo (NVDEC) Selecione como a decodificação de vídeo é realizada durante cutscenes e intros. @@ -435,8 +434,6 @@ Resolução (Portátil/Modo TV) Modo de VSync - Shader Backend - Escolha como os shaders são compilados e traduzidos para sua GPU. Filtro de Adaptação da Janela Nitidez do FSR Determina a nitidez da imagem ao utilizar o contraste dinâmico do FSR diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 271b71e5d7..ff76e2036a 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -47,6 +47,8 @@ Sobreposição do dispositivo Configurar quais informações são mostradas na sobreposição do dispositivo + Mostrar ID da Build + Mostrar Versão do Driver Mostrar modelo do dispositivo Mostrar modelo da GPU Mostrar modelo do SoC @@ -63,9 +65,7 @@ Ativar Emulação de MMU do Anfitrião Esta otimização acelera os acessos à memória pelo programa convidado. Ativar faz com que as leituras/escritas de memória do convidado sejam efetuadas diretamente na memória e utilizem a MMU do Anfitrião. Desativar força todos os acessos à memória a usar a Emulação de MMU por Software. - - GLSL - GLASM + Emulação NVDEC Método de decodificação de vídeo. @@ -362,8 +362,6 @@ Resolução (Portátil/Ancorado) Modo VSync - Backend de Shader - Método de compilação de shaders. Filtro de Adaptação da Janela Nitidez do FSR Determina a nitidez da imagem ao usar contraste dinâmico do FSR diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 34bdb0d5cc..232d155ffd 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -68,6 +68,8 @@ Оверлей устройства Настроить, какая информация отображается в оверлее устройства + Показать ID сборки + Показать версию драйвера Показать модель устройства Показать модель ГПУ Показать модель SoC @@ -88,9 +90,6 @@ Только для разработчиков От 0 до 65535 - - GLSL - GLASM Эмуляция NVDEC Обработка видео (ролики, интро) @@ -437,8 +436,6 @@ Разрешение (портативное/в док-станции) Режим верт. синхронизации - Шейдерный бэкенд - Метод компиляции шейдеров Фильтр адаптации окна Резкость FSR Определяет, насколько чётким будет изображение при использовании динамического контраста FSR. diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index c9ede9a032..25964d188d 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -45,6 +45,8 @@ Прекривање уређаја Конфигуришите које се информације приказују у прекривању уређаја + Прикажи ID билда + Прикажи верзију драјвера Прикажи модел уређаја Прикажи ГПУ модел Прикажи соц модел @@ -61,9 +63,7 @@ Омогући емулацију MMU домаћина Ова оптимизација убрзава приступ меморији од стране гостујућег програма. Укључивање изазива да се читања/уписа меморије госта обављају директно у меморији и користе MMU домаћина. Искључивање присиљава све приступе меморији да користе софтверску емулацију MMU. - - GLSL - GLASM + НВДЕЦ Емулација Изаберите како се видео декодирање (НВДЕЦ) обрађује током секс и увозних интросија. @@ -361,8 +361,6 @@ Резолуција (ручно / прикључено) Всинц мод - Схадер Бацкенд - Изаберите како се сјеначици саставе и преведете за ваш ГПУ. Филтер прилагођавања прозора ФСР оштрина Одређује колико ће се слика наоштрен трајати док користи \"ФСР\" динамички контраст diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 21995b3916..6aea111ea0 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -68,6 +68,8 @@ Накладання пристрою Налаштувати, яка інформація відображається в накладанні пристрою + Показати ID збірки + Показати версію драйвера Показати модель пристрою Показати модель GPU Показати модель SoC @@ -88,11 +90,6 @@ Лише для розробників. 0–65535 - - GLSL - GLASM - SPIRV - Емуляція NVDEC Обробка відео під час катсцен @@ -442,8 +439,6 @@ Роздільна здатність (Портативний/Док) Режим верт. синхронізації - Система обробки шейдерів - Виберіть, як компілювати й транслювати шейдери для ГП. Фільтр масштабування вікна Різкість FSR Визначає різкість зображення при використанні FSR. diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index 948378d075..2f6b80a2af 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -47,6 +47,8 @@ Lớp phủ thiết bị Cấu hình thông tin hiển thị trong lớp phủ thiết bị + Hiển thị ID build + Hiển thị phiên bản driver Hiển thị model thiết bị Hiển thị model GPU Hiển thị model SoC @@ -63,9 +65,7 @@ Bật giả lập MMU Máy chủ Tối ưu hóa này tăng tốc độ truy cập bộ nhớ của chương trình khách. Bật nó lên khiến các thao tác đọc/ghi bộ nhớ khách được thực hiện trực tiếp vào bộ nhớ và sử dụng MMU của Máy chủ. Tắt tính năng này buộc tất cả quyền truy cập bộ nhớ phải sử dụng Giả lập MMU Phần mềm. - - GLSL - GLASM + Giả lập NVDEC Chọn cách xử lý giải mã video @@ -337,8 +337,6 @@ Độ phân giải (Handheld/Docked) Chế độ VSync - Backend Shader - Chọn cách biên dịch shader Bộ lọc điều chỉnh cửa sổ Độ sắc nét FSR Độ sắc nét khi dùng FSR diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index e2544cc835..afafbaed2e 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -65,6 +65,8 @@ 设备叠加层 配置设备叠加层中显示的信息 + 显示构建ID + 显示驱动版本 显示设备型号 显示GPU型号 显示SoC型号 @@ -85,9 +87,6 @@ 仅用于开发用途。 0 到 65535 - - GLSL - GLASM NVDEC模拟 选择视频解码处理方式 @@ -428,8 +427,6 @@ 分辨率 (掌机模式/主机模式) 垂直同步模式 - 着色器后端 - 选择着色器编译方式 窗口滤镜 FSR 锐化度 指定使用 FSR 时图像的锐化程度 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 3d39cccf6c..11d377b6ac 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -68,6 +68,8 @@ 裝置浮層 設定裝置浮層中顯示的資訊 + 顯示建構ID + 顯示驅動程式版本 顯示裝置型號 顯示GPU型號 顯示SoC型號 @@ -88,9 +90,6 @@ 僅限開發用途 0到65535 - - GLSL - GLASM NVDEC模擬 選擇影片解碼(NVDEC)的方式 @@ -431,8 +430,6 @@ 解析度 (手提/底座) 垂直同步 - 著色器後端 - 選擇著色器的編譯與轉譯方式 視窗適應過濾器 FSR 銳化度 使用 FSR 時圖片的銳化程度 diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 454a2d478a..69f1590844 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -111,18 +111,6 @@ 2 - - @string/shader_backend_glsl - @string/shader_backend_glasm - @string/shader_backend_spirv - - - - 0 - 1 - 2 - - @string/vram_usage_conservative @@ -564,6 +552,54 @@ 2 + + @string/gpu_texturesizeswizzle_verysmall + @string/gpu_texturesizeswizzle_small + @string/gpu_texturesizeswizzle_normal + @string/gpu_texturesizeswizzle_large + @string/gpu_texturesizeswizzle_verylarge + + + + 0 + 1 + 2 + 3 + 4 + + + + @string/gpu_swizzle_verylow + @string/gpu_swizzle_low + @string/gpu_swizzle_normal + @string/gpu_swizzle_medium + @string/gpu_swizzle_high + + + + 0 + 1 + 2 + 3 + 4 + + + + @string/gpu_swizzlechunk_verylow + @string/gpu_swizzlechunk_low + @string/gpu_swizzlechunk_normal + @string/gpu_swizzlechunk_medium + @string/gpu_swizzlechunk_high + + + + 0 + 1 + 2 + 3 + 4 + + @string/temperature_celsius @string/temperature_fahrenheit diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0fbb4b6331..6c2dc1ff02 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -21,6 +21,7 @@ Value must be at most %1$d Invalid value + Using Per-Game Config Show Input Overlay @@ -85,6 +86,8 @@ Device Overlay Configure what information is shown in the device overlay + Show Build ID + Show Driver Version Show Device Model Show GPU Model Show SoC Model @@ -105,16 +108,11 @@ For development use only. 0 to 65535 - - GLSL - GLASM - SPIRV - NVDEC Emulation Select how video decoding (NVDEC) is handled during cutscenes and intros. - CPU - GPU + CPU + GPU None @@ -401,7 +399,7 @@ An open-source Switch emulator Contributors Contributors who made Eden for Android possible - https://git.eden-emu.dev/eden-emu/eden/activity/contributors + https://git.eden-emu.dev/eden-emu/eden/activity/contributors Projects that make Eden for Android possible Build User data @@ -461,8 +459,6 @@ Resolution (Handheld/Docked) VSync mode - Shader Backend - Choose how shaders are compiled and translated for your GPU. Window adapting filter FSR sharpness Determines how sharpened the image will look while using FSR\'s dynamic contrast @@ -502,8 +498,17 @@ Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity. Skip CPU Inner Invalidation Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games. + Fix Bloom Effects + Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout Use asynchronous shaders Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches. + GPU Unswizzle Max Texture Size + Sets the maximum size (MB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. + GPU Unswizzle Stream Size + Sets the data limit per frame for unswizzling large textures. Higher values speed up texture loading at the cost of higher frame latency; lower values reduce GPU overhead but may cause visible texture pop-in. + GPU Unswizzle Chunk Size + Defines the number of depth slices processed per batch for 3D textures. Increasing this improves throughput efficiency on powerful GPUs but may cause stuttering or driver timeouts on weaker hardware. + Extensions @@ -576,10 +581,10 @@ Buttons - A - B - X - Y + A + B + X + Y Plus Minus Home @@ -600,15 +605,15 @@ Modifier Modifier range Triggers - L - R - ZL - ZR + L + R + ZL + ZR Left SL Left SR Right SL Right SR - Z + Z Invalid Not set Unknown @@ -729,6 +734,7 @@ Docked mode, region, language Graphics Accuracy level, resolution, shader cache + Quick Settings Audio Output engine, volume Controls @@ -815,7 +821,7 @@ Confirm uninstall Are you sure you want to uninstall this addon? Verify integrity - Verifying… + Verifying... Integrity verification succeeded! Integrity verification failed! File contents may be corrupt @@ -946,6 +952,27 @@ Medium (256) High (512) + + Very Small (16 MB) + Small (32 MB) + Normal (128 MB) + Large (256 MB) + Very Large (512 MB) + + + Very Low (4 MB) + Low (8 MB) + Normal (16 MB) + Medium (32 MB) + High (64 MB) + + + Very Low (32) + Low (64) + Normal (128) + Medium (256) + High (512) + Celsius Fahrenheit @@ -981,7 +1008,7 @@ EB - Vulkan + Vulkan None @@ -995,29 +1022,29 @@ Safe - CPU - GPU - CPU Asynchronously + CPU + GPU + CPU Async Uncompressed - BC1 (Low Quality) - BC3 (Medium Quality) + BC1 + BC3 Conservative Aggressive - 0.25X (180p/270p) - 0.5X (360p/540p) - 0.75X (540p/810p) - 1X (720p/1080p) - 1.25X (900p/1350p) - 1.5X (1080p/1620p) - 2X (1440p/2160p) (Slow) - 3X (2160p/3240p) (Slow) - 4X (2880p/4320p) (Slow) + 0.25X (180p/270p) + 0.5X (360p/540p) + 0.75X (540p/810p) + 1X (720p/1080p) + 1.25X (900p/1350p) + 1.5X (1080p/1620p) + 2X (1440p/2160p) (Slow) + 3X (2160p/3240p) (Slow) + 4X (2880p/4320p) (Slow) Immediate (Off) @@ -1026,24 +1053,24 @@ FIFO Relaxed - Nearest Neighbor - Bilinear - Bicubic - Spline-1 - Gaussian - Lanczos - ScaleForce - AMD FidelityFX™ Super Resolution - Area - Zero-Tangent - B-Spline - Mitchell - MMPX + Nearest Neighbor + Bilinear + Bicubic + Spline-1 + Gaussian + Lanczos + ScaleForce + AMD FidelityFX Super Resolution + Area + Zero-Tangent + B-Spline + Mitchell + MMPX None - FXAA - SMAA + FXAA + SMAA Auto @@ -1055,15 +1082,15 @@ Reverse portrait - Default (16:9) - Force 4:3 - Force 21:9 - Force 16:10 + 16:9 + 4:3 + 21:9 + 16:10 Stretch to window - Dynarmic (JIT) - Native code execution (NCE) + Dynarmic (JIT) + NCE Accurate @@ -1117,16 +1144,16 @@ Dark - oboe - cubeb + oboe + cubeb - x2 - x4 - x8 - x16 - x32 - x64 + x2 + x4 + x8 + x16 + x32 + x64 None @@ -1137,30 +1164,30 @@ App Language Change the language of the app interface Follow System - English - Español - Français - Deutsch - Italiano - Português - Português do Brasil - Русский - 日本語 - 한국어 - 简体中文 - 繁體中文 - Polski - Čeština - Norsk bokmål - Magyar - Українська - Tiếng Việt - Bahasa Indonesia - العربية - کوردیی ناوەندی - فارسی - עברית - Српски + English + Español + Français + Deutsch + Italiano + Português + Português do Brasil + Русский + 日本語 + 한국어 + 简体中文 + 繁體中文 + Polski + Čeština + Norsk bokmål + Magyar + Українська + Tiếng Việt + Bahasa Indonesia + العربية + کوردیی ناوەندی + فارسی + עברית + Српски Theme Color diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp index 48f0a63d4a..a578461f7c 100644 --- a/src/audio_core/adsp/adsp.cpp +++ b/src/audio_core/adsp/adsp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,8 +10,8 @@ namespace AudioCore::ADSP { ADSP::ADSP(Core::System& system, Sink::Sink& sink) { - audio_renderer = std::make_unique(system, sink); - opus_decoder = std::make_unique(system); + audio_renderer.emplace(system, sink); + opus_decoder.emplace(system); opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { LOG_ERROR(Service_Audio, "OpusDecoder failed to initialize."); @@ -17,11 +20,11 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) { } AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { - return *audio_renderer.get(); + return *audio_renderer; } OpusDecoder::OpusDecoder& ADSP::OpusDecoder() { - return *opus_decoder.get(); + return *opus_decoder; } } // namespace AudioCore::ADSP diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h index a0c24a16a2..028d87939d 100644 --- a/src/audio_core/adsp/adsp.h +++ b/src/audio_core/adsp/adsp.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include + #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" #include "audio_core/adsp/apps/opus/opus_decoder.h" #include "common/common_types.h" @@ -45,8 +50,8 @@ public: private: /// AudioRenderer app - std::unique_ptr audio_renderer{}; - std::unique_ptr opus_decoder{}; + std::optional audio_renderer{}; + std::optional opus_decoder{}; }; } // namespace ADSP diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 0ee3225bf6..34a396e2dc 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -113,6 +113,8 @@ public: break; } + session.ReleaseBuffer(buffers[index]); + ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count()); buffer_released = true; } diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp index e60a7d48d4..7d0cce74db 100644 --- a/src/audio_core/opus/decoder.cpp +++ b/src/audio_core/opus/decoder.cpp @@ -27,33 +27,31 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) OpusDecoder::~OpusDecoder() { if (decode_object_initialized) { - hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size); + hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size()); } } -Result OpusDecoder::Initialize(const OpusParametersEx& params, - Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { +Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { auto frame_size{params.use_large_frame_size ? 5760 : 1920}; - shared_buffer_size = transfer_memory_size; - shared_buffer = std::make_unique(shared_buffer_size); + shared_buffer.resize(transfer_memory_size); shared_memory_mapped = true; buffer_size = Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); - out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; + out_data = {shared_buffer.data() + shared_buffer.size() - buffer_size, buffer_size}; size_t in_data_size{0x600u}; in_data = {out_data.data() - in_data_size, in_data_size}; ON_RESULT_FAILURE { if (shared_memory_mapped) { shared_memory_mapped = false; - ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); + ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.data(), shared_buffer.size()))); } }; R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count, - shared_buffer.get(), shared_buffer_size)); + shared_buffer.data(), shared_buffer.size())); sample_rate = params.sample_rate; channel_count = params.channel_count; @@ -62,31 +60,29 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, R_SUCCEED(); } -Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, - Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { +Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { auto frame_size{params.use_large_frame_size ? 5760 : 1920}; - shared_buffer_size = transfer_memory_size; - shared_buffer = std::make_unique(shared_buffer_size); + shared_buffer.resize(transfer_memory_size, 0); shared_memory_mapped = true; buffer_size = Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); - out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; + out_data = {shared_buffer.data() + shared_buffer.size() - buffer_size, buffer_size}; size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)}; in_data = {out_data.data() - in_data_size, in_data_size}; ON_RESULT_FAILURE { if (shared_memory_mapped) { shared_memory_mapped = false; - ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); + ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.data(), shared_buffer.size()))); } }; R_TRY(hardware_opus.InitializeMultiStreamDecodeObject( params.sample_rate, params.channel_count, params.total_stream_count, - params.stereo_stream_count, params.mappings.data(), shared_buffer.get(), - shared_buffer_size)); + params.stereo_stream_count, params.mappings.data(), shared_buffer.data(), + shared_buffer.size())); sample_rate = params.sample_rate; channel_count = params.channel_count; @@ -113,7 +109,7 @@ Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, ResultBufferTooSmall); if (!shared_memory_mapped) { - R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); + R_TRY(hardware_opus.MapMemory(shared_buffer.data(), shared_buffer.size())); shared_memory_mapped = true; } @@ -121,7 +117,7 @@ Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(), header.size, - shared_buffer.get(), time_taken, reset)); + shared_buffer.data(), time_taken, reset)); std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); @@ -136,7 +132,7 @@ Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, Result OpusDecoder::SetContext([[maybe_unused]] std::span context) { R_SUCCEED_IF(shared_memory_mapped); shared_memory_mapped = true; - R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); + R_RETURN(hardware_opus.MapMemory(shared_buffer.data(), shared_buffer.size())); } Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, @@ -159,7 +155,7 @@ Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out ResultBufferTooSmall); if (!shared_memory_mapped) { - R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); + R_TRY(hardware_opus.MapMemory(shared_buffer.data(), shared_buffer.size())); shared_memory_mapped = true; } @@ -167,7 +163,7 @@ Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out R_TRY(hardware_opus.DecodeInterleavedForMultiStream( out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(), - header.size, shared_buffer.get(), time_taken, reset)); + header.size, shared_buffer.data(), time_taken, reset)); std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h index 1b8c257d43..33bf88e349 100644 --- a/src/audio_core/opus/decoder.h +++ b/src/audio_core/opus/decoder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -36,8 +39,7 @@ public: private: Core::System& system; HardwareOpus& hardware_opus; - std::unique_ptr shared_buffer{}; - u64 shared_buffer_size; + std::vector shared_buffer{}; std::span in_data{}; std::span out_data{}; u64 buffer_size{}; diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 3655ae8f69..2c41e7f2c8 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -151,7 +151,17 @@ void SinkStream::ProcessAudioIn(std::span input_buffer, std::size_t n playing_buffer.consumed = true; } - std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); + if (frames_written > 0) { + std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); + } + + // update sample counts für audio-ins + { + std::scoped_lock lk{sample_count_lock}; + last_sample_count_update_time = system.CoreTiming().GetGlobalTimeNs(); + min_played_sample_count = max_played_sample_count; + max_played_sample_count += frames_written; + } } void SinkStream::ProcessAudioOutAndRender(std::span output_buffer, std::size_t num_frames) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a3d57ffce4..3d09c1caea 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -5,243 +5,242 @@ # GPL-2.0-or-later if(DEFINED ENV{AZURECIREPO}) - set(BUILD_REPOSITORY $ENV{AZURECIREPO}) + set(BUILD_REPOSITORY $ENV{AZURECIREPO}) endif() if(DEFINED ENV{TITLEBARFORMATIDLE}) - set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE}) + set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE}) endif() if(DEFINED ENV{TITLEBARFORMATRUNNING}) - set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING}) + set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING}) endif() if(DEFINED ENV{DISPLAYVERSION}) - set(DISPLAY_VERSION $ENV{DISPLAYVERSION}) + set(DISPLAY_VERSION $ENV{DISPLAYVERSION}) endif() include(GenerateSCMRev) add_library( - common STATIC - address_space.cpp - address_space.h - algorithm.h - alignment.h - announce_multiplayer_room.h - assert.cpp - assert.h - atomic_ops.h - bit_field.h - bit_util.h - bounded_threadsafe_queue.h - cityhash.cpp - cityhash.h - common_funcs.h - common_types.h - concepts.h - container_hash.h - demangle.cpp - demangle.h - detached_tasks.cpp - detached_tasks.h - device_power_state.cpp - device_power_state.h - div_ceil.h - dynamic_library.cpp - dynamic_library.h - elf.h - error.cpp - error.h - expected.h - fiber.cpp - fiber.h - fixed_point.h - free_region_manager.h - fs/file.cpp - fs/file.h - fs/fs.cpp - fs/fs.h - fs/fs_paths.h - fs/fs_types.h - fs/fs_util.cpp - fs/fs_util.h - fs/path_util.cpp - fs/path_util.h - hash.h - heap_tracker.cpp - heap_tracker.h - hex_util.cpp - hex_util.h - host_memory.cpp - host_memory.h - input.h - intrusive_red_black_tree.h - literals.h - logging/backend.cpp - logging/backend.h - logging/filter.cpp - logging/filter.h - logging/formatter.h - logging/log.h - logging/log_entry.h - logging/text_formatter.cpp - logging/text_formatter.h - logging/types.h - lz4_compression.cpp - lz4_compression.h - make_unique_for_overwrite.h - math_util.h - memory_detect.cpp - memory_detect.h - multi_level_page_table.cpp - multi_level_page_table.h - overflow.h - page_table.cpp - page_table.h - param_package.cpp - param_package.h - parent_of_member.h - point.h - quaternion.h - range_map.h - range_mutex.h - range_sets.h - range_sets.inc - ring_buffer.h - ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp - scm_rev.h - scope_exit.h - scratch_buffer.h - settings.cpp - settings.h - settings_common.cpp - settings_common.h - settings_enums.h - settings_input.cpp - settings_input.h - settings_setting.h - slot_vector.h - socket_types.h - spin_lock.h - stb.cpp - stb.h - steady_clock.cpp - steady_clock.h - stream.cpp - stream.h - string_util.cpp - string_util.h - swap.h - thread.cpp - thread.h - thread_queue_list.h - thread_worker.h - threadsafe_queue.h - time_zone.cpp - time_zone.h - tiny_mt.h - tree.h - typed_address.h - uint128.h - unique_function.h - uuid.cpp - uuid.h - vector_math.h - virtual_buffer.cpp - virtual_buffer.h - wall_clock.cpp - wall_clock.h - zstd_compression.cpp - zstd_compression.h - fs/ryujinx_compat.h fs/ryujinx_compat.cpp - fs/symlink.h fs/symlink.cpp -) + common STATIC + address_space.cpp + address_space.h + algorithm.h + alignment.h + announce_multiplayer_room.h + assert.cpp + assert.h + atomic_ops.h + bit_field.h + bit_util.h + bounded_threadsafe_queue.h + cityhash.cpp + cityhash.h + common_funcs.h + common_types.h + concepts.h + container_hash.h + demangle.cpp + demangle.h + detached_tasks.cpp + detached_tasks.h + device_power_state.cpp + device_power_state.h + div_ceil.h + dynamic_library.cpp + dynamic_library.h + elf.h + error.cpp + error.h + expected.h + fiber.cpp + fiber.h + fixed_point.h + free_region_manager.h + fs/file.cpp + fs/file.h + fs/fs.cpp + fs/fs.h + fs/fs_paths.h + fs/fs_types.h + fs/fs_util.cpp + fs/fs_util.h + fs/path_util.cpp + fs/path_util.h + hash.h + heap_tracker.cpp + heap_tracker.h + hex_util.cpp + hex_util.h + host_memory.cpp + host_memory.h + input.h + intrusive_red_black_tree.h + literals.h + logging/backend.cpp + logging/backend.h + logging/filter.cpp + logging/filter.h + logging/formatter.h + logging/log.h + logging/log_entry.h + logging/text_formatter.cpp + logging/text_formatter.h + logging/types.h + lz4_compression.cpp + lz4_compression.h + make_unique_for_overwrite.h + math_util.h + memory_detect.cpp + memory_detect.h + multi_level_page_table.cpp + multi_level_page_table.h + overflow.h + page_table.cpp + page_table.h + param_package.cpp + param_package.h + parent_of_member.h + point.h + quaternion.h + range_map.h + range_mutex.h + range_sets.h + range_sets.inc + ring_buffer.h + ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp + scm_rev.h + scope_exit.h + scratch_buffer.h + settings.cpp + settings.h + settings_common.cpp + settings_common.h + settings_enums.h + settings_input.cpp + settings_input.h + settings_setting.h + slot_vector.h + socket_types.h + spin_lock.h + stb.cpp + stb.h + steady_clock.cpp + steady_clock.h + stream.cpp + stream.h + string_util.cpp + string_util.h + swap.h + thread.cpp + thread.h + thread_queue_list.h + thread_worker.h + threadsafe_queue.h + time_zone.cpp + time_zone.h + tiny_mt.h + tree.h + typed_address.h + uint128.h + unique_function.h + uuid.cpp + uuid.h + vector_math.h + virtual_buffer.cpp + virtual_buffer.h + wall_clock.cpp + wall_clock.h + zstd_compression.cpp + zstd_compression.h + fs/ryujinx_compat.h fs/ryujinx_compat.cpp + fs/symlink.h fs/symlink.cpp) if(WIN32) - target_sources(common PRIVATE windows/timer_resolution.cpp - windows/timer_resolution.h) - target_link_libraries(common PRIVATE ntdll) + target_sources(common PRIVATE windows/timer_resolution.cpp + windows/timer_resolution.h) + target_link_libraries(common PRIVATE ntdll) endif() if(NOT WIN32) - target_sources(common PRIVATE signal_chain.cpp signal_chain.h) + target_sources(common PRIVATE signal_chain.cpp signal_chain.h) endif() if(ANDROID) - target_sources( - common - PUBLIC fs/fs_android.cpp - fs/fs_android.h - android/android_common.cpp - android/android_common.h - android/id_cache.cpp - android/id_cache.h - android/multiplayer/multiplayer.cpp - android/multiplayer/multiplayer.h - android/applets/software_keyboard.cpp - android/applets/software_keyboard.h) + target_sources( + common + PUBLIC fs/fs_android.cpp + fs/fs_android.h + android/android_common.cpp + android/android_common.h + android/id_cache.cpp + android/id_cache.h + android/multiplayer/multiplayer.cpp + android/multiplayer/multiplayer.h + android/applets/software_keyboard.cpp + android/applets/software_keyboard.h + android/applets/web_browser.cpp + android/applets/web_browser.h) endif() if(ARCHITECTURE_x86_64) - target_sources( - common - PRIVATE x64/cpu_detect.cpp - x64/cpu_detect.h - x64/cpu_wait.cpp - x64/cpu_wait.h - x64/native_clock.cpp - x64/native_clock.h - x64/rdtsc.cpp - x64/rdtsc.h - x64/xbyak_abi.h - x64/xbyak_util.h) - target_link_libraries(common PRIVATE xbyak::xbyak) + target_sources( + common + PRIVATE x64/cpu_detect.cpp + x64/cpu_detect.h + x64/cpu_wait.cpp + x64/cpu_wait.h + x64/native_clock.cpp + x64/native_clock.h + x64/rdtsc.cpp + x64/rdtsc.h + x64/xbyak_abi.h + x64/xbyak_util.h) + target_link_libraries(common PRIVATE xbyak::xbyak) endif() if(HAS_NCE) - target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h) + target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h) endif() if(MSVC) - target_compile_definitions( - common - PRIVATE # The standard library doesn't provide any replacement for codecvt - # yet so we can disable this deprecation warning for the time being. - _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) - target_compile_options( - common - PRIVATE /we4242 # 'identifier': conversion from 'type1' to 'type2', possible - # loss of data - /we4254 # 'operator': conversion from 'type1:field_bits' to - # 'type2:field_bits', possible loss of data - /we4800 # Implicit conversion from 'type' to bool. Possible - # information loss - ) + target_compile_definitions( + common + PRIVATE # The standard library doesn't provide any replacement for codecvt + # yet so we can disable this deprecation warning for the time being. + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) + target_compile_options( + common + PRIVATE /we4242 # 'identifier': conversion from 'type1' to 'type2', possible + # loss of data + /we4254 # 'operator': conversion from 'type1:field_bits' to + # 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible + # information loss + ) else() - set_source_files_properties( - stb.cpp - PROPERTIES - COMPILE_OPTIONS - "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers" - ) + set_source_files_properties( + stb.cpp + PROPERTIES + COMPILE_OPTIONS + "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers") - # Get around GCC failing with intrinsics in Debug - if(CXX_GCC AND CMAKE_BUILD_TYPE MATCHES "Debug") - set_property( - SOURCE stb.cpp - APPEND - PROPERTY COMPILE_OPTIONS ";-O2") - endif() + # Get around GCC failing with intrinsics in Debug + if(CXX_GCC AND CMAKE_BUILD_TYPE MATCHES "Debug") + set_property( + SOURCE stb.cpp + APPEND + PROPERTY COMPILE_OPTIONS ";-O2") + endif() endif() if(CXX_CLANG) - target_compile_options(common PRIVATE -fsized-deallocation - -Werror=unreachable-code-aggressive) - target_compile_definitions( - common - PRIVATE - # Clang 14 and earlier have errors when explicitly instantiating - # Settings::Setting - $<$,15>:CANNOT_EXPLICITLY_INSTANTIATE> - ) + target_compile_options(common PRIVATE -fsized-deallocation + -Werror=unreachable-code-aggressive) + target_compile_definitions( + common + PRIVATE + # Clang 14 and earlier have errors when explicitly instantiating + # Settings::Setting + $<$,15>:CANNOT_EXPLICITLY_INSTANTIATE>) endif() if (BOOST_NO_HEADERS) @@ -260,8 +259,8 @@ target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) target_link_libraries(common PRIVATE lz4::lz4 LLVM::Demangle zstd::zstd) if(ANDROID) - # For ASharedMemory_create - target_link_libraries(common PRIVATE android) + # For ASharedMemory_create + target_link_libraries(common PRIVATE android) endif() create_target_directory_groups(common) diff --git a/src/common/android/applets/web_browser.cpp b/src/common/android/applets/web_browser.cpp new file mode 100644 index 0000000000..cf844ff5fc --- /dev/null +++ b/src/common/android/applets/web_browser.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/android/android_common.h" +#include "common/android/id_cache.h" +#include "common/android/applets/web_browser.h" +#include "common/logging/log.h" + +static jclass s_native_library_class = nullptr; +static jmethodID s_open_external_url = nullptr; + +namespace Common::Android::WebBrowser { + +void InitJNI(JNIEnv* env) { + const jclass local = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); + s_native_library_class = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + s_open_external_url = env->GetStaticMethodID(s_native_library_class, "openExternalUrl", "(Ljava/lang/String;)V"); +} + +void CleanupJNI(JNIEnv* env) { + if (s_native_library_class != nullptr) { + env->DeleteGlobalRef(s_native_library_class); + s_native_library_class = nullptr; + } + s_open_external_url = nullptr; +} + +void AndroidWebBrowser::OpenLocalWebPage(const std::string& local_url, ExtractROMFSCallback extract_romfs_callback, OpenWebPageCallback callback) const { + LOG_WARNING(Frontend, "(STUBBED)"); + callback(Service::AM::Frontend::WebExitReason::WindowClosed, ""); +} + +void AndroidWebBrowser::OpenExternalWebPage(const std::string& external_url, OpenWebPageCallback callback) const { + // do a dedicated thread, calling from the this thread crashed CPU fiber. + Common::Android::RunJNIOnFiber([&](JNIEnv* env) { + if (env != nullptr && s_native_library_class != nullptr && s_open_external_url != nullptr) { + const jstring j_url = Common::Android::ToJString(env, external_url); + env->CallStaticVoidMethod(s_native_library_class, s_open_external_url, j_url); + env->DeleteLocalRef(j_url); + } else { + LOG_ERROR(Frontend, "JNI not initialized, cannot open {}", external_url); + } + return; + }); + + callback(Service::AM::Frontend::WebExitReason::WindowClosed, external_url); +} + +} // namespace Common::Android::WebBrowser diff --git a/src/common/android/applets/web_browser.h b/src/common/android/applets/web_browser.h new file mode 100644 index 0000000000..a70903e3be --- /dev/null +++ b/src/common/android/applets/web_browser.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "core/frontend/applets/web_browser.h" + +namespace Common::Android::WebBrowser { + +class AndroidWebBrowser final : public Core::Frontend::WebBrowserApplet { +public: + ~AndroidWebBrowser() override = default; + + void Close() const override {} + + void OpenLocalWebPage(const std::string& local_url, + ExtractROMFSCallback extract_romfs_callback, + OpenWebPageCallback callback) const override; + + void OpenExternalWebPage(const std::string& external_url, + OpenWebPageCallback callback) const override; +}; + +void InitJNI(JNIEnv* env); +void CleanupJNI(JNIEnv* env); + +} // namespace Common::Android::WebBrowser diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 1198833996..eb43f4e213 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -4,6 +4,7 @@ #include #include "applets/software_keyboard.h" +#include "applets/web_browser.h" #include "common/android/id_cache.h" #include "common/assert.h" #include "common/fs/fs_android.h" @@ -602,6 +603,7 @@ namespace Common::Android { // Initialize applets Common::Android::SoftwareKeyboard::InitJNI(env); + Common::Android::WebBrowser::InitJNI(env); return JNI_VERSION; } @@ -631,6 +633,7 @@ namespace Common::Android { // UnInitialize applets SoftwareKeyboard::CleanupJNI(env); + WebBrowser::CleanupJNI(env); AndroidMultiplayer::NetworkShutdown(); } diff --git a/src/common/assert.h b/src/common/assert.h index 8edb020695..f720f90f15 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project @@ -16,6 +16,7 @@ void AssertFailSoftImpl(); [[noreturn]] void AssertFatalImpl(); +// Prevents errors on old GCC... smh... #ifdef _MSC_VER #define YUZU_NO_INLINE __declspec(noinline) #else @@ -23,9 +24,9 @@ void AssertFailSoftImpl(); #endif #define ASSERT_MSG(_a_, ...) \ - ([&]() YUZU_NO_INLINE { \ + ([&]() YUZU_NO_INLINE { \ if (!(_a_)) [[unlikely]] { \ - LOG_CRITICAL(Debug, __FILE__ ": assert\n" __VA_ARGS__); \ + LOG_CRITICAL(Debug, __FILE__ ": assert " __VA_ARGS__); \ AssertFailSoftImpl(); \ } \ }()) @@ -33,7 +34,7 @@ void AssertFailSoftImpl(); #define UNREACHABLE_MSG(...) \ do { \ - LOG_CRITICAL(Debug, __FILE__ ": unreachable\n" __VA_ARGS__); \ + LOG_CRITICAL(Debug, __FILE__ ": unreachable " __VA_ARGS__); \ AssertFatalImpl(); \ } while (0) #define UNREACHABLE() UNREACHABLE_MSG("") @@ -50,10 +51,10 @@ void AssertFailSoftImpl(); } while (0) #endif -#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!") +#define UNIMPLEMENTED() ASSERT(false && "Unimplemented!") #define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__) -#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") +#define UNIMPLEMENTED_IF(cond) ASSERT((!(cond)) && "Unimplemented!") #define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) // If the assert is ignored, execute _b_ diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 4f0f2b6430..ea3da3d053 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" @@ -14,100 +15,70 @@ namespace Common { -constexpr std::size_t default_stack_size = 512 * 1024; +constexpr size_t DEFAULT_STACK_SIZE = 128 * 4096; +constexpr u32 CANARY_VALUE = 0xDEADBEEF; struct Fiber::FiberImpl { - FiberImpl() : stack{default_stack_size}, rewind_stack{default_stack_size} {} + FiberImpl() {} - VirtualBuffer stack; - VirtualBuffer rewind_stack; + std::array stack{}; + std::array rewind_stack{}; + u32 canary = CANARY_VALUE; + + boost::context::detail::fcontext_t context{}; + boost::context::detail::fcontext_t rewind_context{}; std::mutex guard; std::function entry_point; std::function rewind_point; std::shared_ptr previous_fiber; - bool is_thread_fiber{}; - bool released{}; - u8* stack_limit{}; - u8* rewind_stack_limit{}; - boost::context::detail::fcontext_t context{}; - boost::context::detail::fcontext_t rewind_context{}; + u8* stack_limit = nullptr; + u8* rewind_stack_limit = nullptr; + bool is_thread_fiber = false; + bool released = false; }; void Fiber::SetRewindPoint(std::function&& rewind_func) { impl->rewind_point = std::move(rewind_func); } -void Fiber::Start(boost::context::detail::transfer_t& transfer) { - ASSERT(impl->previous_fiber != nullptr); - impl->previous_fiber->impl->context = transfer.fctx; - impl->previous_fiber->impl->guard.unlock(); - impl->previous_fiber.reset(); - impl->entry_point(); - UNREACHABLE(); -} - -void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) { - ASSERT(impl->context != nullptr); - impl->context = impl->rewind_context; - impl->rewind_context = nullptr; - u8* tmp = impl->stack_limit; - impl->stack_limit = impl->rewind_stack_limit; - impl->rewind_stack_limit = tmp; - impl->rewind_point(); - UNREACHABLE(); -} - -void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { - auto* fiber = static_cast(transfer.data); - fiber->Start(transfer); -} - -void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) { - auto* fiber = static_cast(transfer.data); - fiber->OnRewind(transfer); -} - Fiber::Fiber(std::function&& entry_point_func) : impl{std::make_unique()} { impl->entry_point = std::move(entry_point_func); impl->stack_limit = impl->stack.data(); impl->rewind_stack_limit = impl->rewind_stack.data(); - u8* stack_base = impl->stack_limit + default_stack_size; - impl->context = - boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc); + u8* stack_base = impl->stack_limit + DEFAULT_STACK_SIZE; + impl->context = boost::context::detail::make_fcontext(stack_base, impl->stack.size(), [](boost::context::detail::transfer_t transfer) -> void { + auto* fiber = static_cast(transfer.data); + ASSERT(fiber && fiber->impl && fiber->impl->previous_fiber && fiber->impl->previous_fiber->impl); + ASSERT(fiber->impl->canary == CANARY_VALUE); + fiber->impl->previous_fiber->impl->context = transfer.fctx; + fiber->impl->previous_fiber->impl->guard.unlock(); + fiber->impl->previous_fiber.reset(); + fiber->impl->entry_point(); + UNREACHABLE(); + }); } Fiber::Fiber() : impl{std::make_unique()} {} Fiber::~Fiber() { - if (impl->released) { - return; - } - // Make sure the Fiber is not being used - const bool locked = impl->guard.try_lock(); - ASSERT_MSG(locked, "Destroying a fiber that's still running"); - if (locked) { - impl->guard.unlock(); + if (!impl->released) { + // Make sure the Fiber is not being used + const bool locked = impl->guard.try_lock(); + ASSERT_MSG(locked, "Destroying a fiber that's still running"); + if (locked) { + impl->guard.unlock(); + } } } void Fiber::Exit() { ASSERT_MSG(impl->is_thread_fiber, "Exiting non main thread fiber"); - if (!impl->is_thread_fiber) { - return; + if (impl->is_thread_fiber) { + impl->guard.unlock(); + impl->released = true; } - impl->guard.unlock(); - impl->released = true; -} - -void Fiber::Rewind() { - ASSERT(impl->rewind_point); - ASSERT(impl->rewind_context == nullptr); - u8* stack_base = impl->rewind_stack_limit + default_stack_size; - impl->rewind_context = - boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc); - boost::context::detail::jump_fcontext(impl->rewind_context, this); } void Fiber::YieldTo(std::weak_ptr weak_from, Fiber& to) { @@ -115,16 +86,15 @@ void Fiber::YieldTo(std::weak_ptr weak_from, Fiber& to) { to.impl->previous_fiber = weak_from.lock(); auto transfer = boost::context::detail::jump_fcontext(to.impl->context, &to); - // "from" might no longer be valid if the thread was killed if (auto from = weak_from.lock()) { if (from->impl->previous_fiber == nullptr) { - ASSERT_MSG(false, "previous_fiber is nullptr!"); - return; + ASSERT(false && "previous_fiber is nullptr!"); + } else { + from->impl->previous_fiber->impl->context = transfer.fctx; + from->impl->previous_fiber->impl->guard.unlock(); + from->impl->previous_fiber.reset(); } - from->impl->previous_fiber->impl->context = transfer.fctx; - from->impl->previous_fiber->impl->guard.unlock(); - from->impl->previous_fiber.reset(); } } diff --git a/src/common/fiber.h b/src/common/fiber.h index 8af6ae4d3a..eb128f4bb2 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -45,22 +45,12 @@ public: /// Fiber 'from' must be the currently running fiber. static void YieldTo(std::weak_ptr weak_from, Fiber& to); [[nodiscard]] static std::shared_ptr ThreadToFiber(); - void SetRewindPoint(std::function&& rewind_func); - - void Rewind(); - /// Only call from main thread's fiber void Exit(); - private: Fiber(); - - void OnRewind(boost::context::detail::transfer_t& transfer); void Start(boost::context::detail::transfer_t& transfer); - static void FiberStartFunc(boost::context::detail::transfer_t transfer); - static void RewindStartFunc(boost::context::detail::transfer_t transfer); - struct FiberImpl; std::unique_ptr impl; }; diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 17d94fe751..092001e224 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -23,13 +23,14 @@ namespace Common::Log { +// Some IDEs prefer : instead, so let's just do that :) std::string FormatLogMessage(const Entry& entry) { auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000); auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000); char const* class_name = GetLogClassName(entry.log_class); char const* level_name = GetLevelName(entry.log_level); return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, - class_name, level_name, entry.filename, entry.function, entry.line_num, + class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message); } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index f8f008065c..1c28adafe5 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -57,7 +57,6 @@ SWITCHABLE(Region, true); SWITCHABLE(RendererBackend, true); SWITCHABLE(ScalingFilter, false); SWITCHABLE(SpirvOptimizeMode, true); -SWITCHABLE(ShaderBackend, true); SWITCHABLE(TimeZone, true); SETTING(VSyncMode, true); SWITCHABLE(bool, false); diff --git a/src/common/settings.h b/src/common/settings.h index 76998811ef..2f6157e1d5 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -78,7 +78,6 @@ SWITCHABLE(Region, true); SWITCHABLE(RendererBackend, true); SWITCHABLE(ScalingFilter, false); SWITCHABLE(SpirvOptimizeMode, true); -SWITCHABLE(ShaderBackend, true); SWITCHABLE(TimeZone, true); SETTING(VSyncMode, true); SWITCHABLE(bool, false); @@ -240,7 +239,6 @@ struct Values { Category::Cpu}; SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto, "cpu_accuracy", Category::Cpu}; - SwitchableSetting vtable_bouncing{linkage, true, "vtable_bouncing", Category::Cpu}; SwitchableSetting fast_cpu_time{linkage, CpuClock::Off, "fast_cpu_time", @@ -267,6 +265,9 @@ struct Values { true, true, &use_custom_cpu_ticks}; + + SwitchableSetting vtable_bouncing{linkage, true, "vtable_bouncing", Category::Cpu}; + Setting cpuopt_page_tables{linkage, true, "cpuopt_page_tables", Category::CpuDebug}; Setting cpuopt_block_linking{linkage, true, "cpuopt_block_linking", Category::CpuDebug}; Setting cpuopt_return_stack_buffer{linkage, true, "cpuopt_return_stack_buffer", @@ -311,20 +312,12 @@ struct Values { // Renderer SwitchableSetting renderer_backend{linkage, #if defined(__sun__) || defined(__managarm__) - RendererBackend::OpenGL, + RendererBackend::OpenGL_GLSL, #else - RendererBackend::Vulkan, + RendererBackend::Vulkan, #endif - "backend", Category::Renderer}; - SwitchableSetting shader_backend{linkage, -#if defined(__sun__) || defined(__managarm__) - ShaderBackend::Glsl, -#else - ShaderBackend::SpirV, -#endif - "shader_backend", Category::Renderer, Specialization::RuntimeList}; - SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, - Specialization::RuntimeList}; + "backend", Category::Renderer}; + SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Specialization::RuntimeList}; // Graphics Settings ResolutionScalingInfo resolution_info{}; @@ -510,9 +503,30 @@ struct Values { #endif "async_presentation", Category::RendererHacks}; + SwitchableSetting fix_bloom_effects{linkage, false, "fix_bloom_effects", + Category::RendererHacks}; + SwitchableSetting use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", Category::RendererHacks}; + SwitchableSetting gpu_unswizzle_texture_size{linkage, + GpuUnswizzleSize::Large, + "gpu_unswizzle_texture_size", + Category::RendererHacks, + Specialization::Default}; + + SwitchableSetting gpu_unswizzle_stream_size{linkage, + GpuUnswizzle::Medium, + "gpu_unswizzle_stream_size", + Category::RendererHacks, + Specialization::Default}; + + SwitchableSetting gpu_unswizzle_chunk_size{linkage, + GpuUnswizzleChunk::Medium, + "gpu_unswizzle_chunk_size", + Category::RendererHacks, + Specialization::Default}; + SwitchableSetting dyna_state{linkage, #if defined (_WIN32) ExtendedDynamicState::EDS3, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 6d6e33ada7..2e934621ac 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -131,8 +131,7 @@ ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); -ENUM(RendererBackend, OpenGL, Vulkan, Null); -ENUM(ShaderBackend, Glsl, Glasm, SpirV); +ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); ENUM(GpuAccuracy, Low, Medium, High); ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(CpuBackend, Dynarmic, Nce); @@ -150,6 +149,9 @@ ENUM(ConsoleMode, Handheld, Docked); ENUM(AppletMode, HLE, LLE); ENUM(SpirvOptimizeMode, Never, OnLoad, Always); ENUM(GpuOverclock, Normal, Medium, High) +ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge) +ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High) +ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High) ENUM(TemperatureUnits, Celsius, Fahrenheit) ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3); ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All) diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index e14bf3e651..4f9c240905 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,7 +12,6 @@ #include "common/x64/native_clock.h" #include "common/x64/rdtsc.h" #endif - #ifdef HAS_NCE #include "common/arm64/native_clock.h" #endif @@ -73,8 +75,4 @@ std::unique_ptr CreateOptimalClock() { #endif } -std::unique_ptr CreateStandardWallClock() { - return std::make_unique(); -} - } // namespace Common diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 3a0c43909a..7ad6536930 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -84,8 +87,6 @@ protected: using CPUTickToGPUTickRatio = std::ratio; }; -std::unique_ptr CreateOptimalClock(); - -std::unique_ptr CreateStandardWallClock(); +[[nodiscard]] std::unique_ptr CreateOptimalClock(); } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a961eff8bf..14eb331d24 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2018 yuzu Emulator Project @@ -563,8 +563,14 @@ add_library(core STATIC hle/service/bcat/delivery_cache_storage_service.h hle/service/bcat/news/newly_arrived_event_holder.cpp hle/service/bcat/news/newly_arrived_event_holder.h + hle/service/bcat/news/msgpack.cpp + hle/service/bcat/news/msgpack.h hle/service/bcat/news/news_data_service.cpp hle/service/bcat/news/news_data_service.h + hle/service/bcat/news/builtin_news.cpp + hle/service/bcat/news/builtin_news.h + hle/service/bcat/news/news_storage.cpp + hle/service/bcat/news/news_storage.h hle/service/bcat/news/news_database_service.cpp hle/service/bcat/news/news_database_service.h hle/service/bcat/news/news_service.cpp @@ -922,6 +928,8 @@ add_library(core STATIC hle/service/olsc/olsc.h hle/service/olsc/remote_storage_controller.cpp hle/service/olsc/remote_storage_controller.h + hle/service/olsc/stopper_object.cpp + hle/service/olsc/stopper_object.h hle/service/olsc/transfer_task_list_controller.cpp hle/service/olsc/transfer_task_list_controller.h hle/service/omm/omm.cpp @@ -1125,6 +1133,8 @@ add_library(core STATIC internal_network/sockets.h internal_network/wifi_scanner.cpp internal_network/wifi_scanner.h + launch_timestamp_cache.cpp + launch_timestamp_cache.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/kip.cpp @@ -1157,8 +1167,7 @@ add_library(core STATIC tools/freezer.cpp tools/freezer.h tools/renderdoc.cpp - tools/renderdoc.h -) + tools/renderdoc.h) if (ENABLE_WIFI_SCAN) # find_package(libiw REQUIRED) @@ -1188,8 +1197,7 @@ else() -Werror=conversion -Wno-sign-conversion -Wno-cast-function-type - $<$:-fsized-deallocation> - ) + $<$:-fsized-deallocation>) # pre-clang19 will spam with "OH DID YOU MEAN THIS?" otherwise... if (CXX_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19) target_compile_options(core PRIVATE -Wno-cast-function-type-mismatch) @@ -1205,10 +1213,16 @@ else() target_link_libraries(core PUBLIC Boost::headers) endif() -target_link_libraries(core PRIVATE fmt::fmt nlohmann_json::nlohmann_json RenderDoc::API MbedTLS::mbedcrypto${MBEDTLS_LIB_SUFFIX} MbedTLS::mbedtls${MBEDTLS_LIB_SUFFIX}) -# if (MINGW) -# target_link_libraries(core PRIVATE ws2_32 mswsock wlanapi) -# endif() +target_link_libraries(core PRIVATE + fmt::fmt + nlohmann_json::nlohmann_json + RenderDoc::API + MbedTLS::mbedcrypto${MBEDTLS_LIB_SUFFIX} + MbedTLS::mbedtls${MBEDTLS_LIB_SUFFIX}) + +if (ENABLE_WEB_SERVICE OR ENABLE_OPENSSL) + target_link_libraries(core PRIVATE httplib::httplib) +endif() if (ENABLE_WEB_SERVICE) target_compile_definitions(core PUBLIC ENABLE_WEB_SERVICE) @@ -1230,8 +1244,7 @@ if (HAS_NCE) arm/nce/interpreter_visitor.h arm/nce/patcher.cpp arm/nce/patcher.h - arm/nce/visitor_base.h - ) + arm/nce/visitor_base.h) target_link_libraries(core PRIVATE merry::oaknut) endif() @@ -1251,8 +1264,7 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) hle/service/jit/jit_context.cpp hle/service/jit/jit_context.h hle/service/jit/jit.cpp - hle/service/jit/jit.h - ) + hle/service/jit/jit.h) target_link_libraries(core PRIVATE dynarmic::dynarmic) endif() @@ -1263,6 +1275,7 @@ if(ENABLE_OPENSSL) find_package(OpenSSL REQUIRED) target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto) + target_compile_definitions(core PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) elseif (APPLE) target_sources(core PRIVATE hle/service/ssl/ssl_backend_securetransport.cpp) diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index b57996cb8b..0fa4ca6f06 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -16,170 +16,160 @@ namespace Core { using namespace Common::Literals; -class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { -public: - explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process) - : m_parent{parent}, m_memory(process->GetMemory()), - m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()}, - m_check_memory_access{m_debugger_enabled || - !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {} +DynarmicCallbacks32::DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process) + : m_parent{parent}, m_memory(process->GetMemory()) + , m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()} + , m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} +{} - u8 MemoryRead8(u32 vaddr) override { - CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); - return m_memory.Read8(vaddr); - } - u16 MemoryRead16(u32 vaddr) override { - CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); - return m_memory.Read16(vaddr); - } - u32 MemoryRead32(u32 vaddr) override { - CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); - return m_memory.Read32(vaddr); - } - u64 MemoryRead64(u32 vaddr) override { - CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); - return m_memory.Read64(vaddr); - } - std::optional MemoryReadCode(u32 vaddr) override { - if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) { - return std::nullopt; - } - return m_memory.Read32(vaddr); - } +u8 DynarmicCallbacks32::MemoryRead8(u32 vaddr) { + CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); + return m_memory.Read8(vaddr); +} +u16 DynarmicCallbacks32::MemoryRead16(u32 vaddr) { + CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); + return m_memory.Read16(vaddr); +} +u32 DynarmicCallbacks32::MemoryRead32(u32 vaddr) { + CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); + return m_memory.Read32(vaddr); +} +u64 DynarmicCallbacks32::MemoryRead64(u32 vaddr) { + CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); + return m_memory.Read64(vaddr); +} +std::optional DynarmicCallbacks32::MemoryReadCode(u32 vaddr) { + if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) + return std::nullopt; + return m_memory.Read32(vaddr); +} - void MemoryWrite8(u32 vaddr, u8 value) override { - if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { - m_memory.Write8(vaddr, value); - } +void DynarmicCallbacks32::MemoryWrite8(u32 vaddr, u8 value) { + if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { + m_memory.Write8(vaddr, value); } - void MemoryWrite16(u32 vaddr, u16 value) override { - if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { - m_memory.Write16(vaddr, value); - } +} +void DynarmicCallbacks32::MemoryWrite16(u32 vaddr, u16 value) { + if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { + m_memory.Write16(vaddr, value); } - void MemoryWrite32(u32 vaddr, u32 value) override { - if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { - m_memory.Write32(vaddr, value); - } +} +void DynarmicCallbacks32::MemoryWrite32(u32 vaddr, u32 value) { + if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { + m_memory.Write32(vaddr, value); } - void MemoryWrite64(u32 vaddr, u64 value) override { - if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { - m_memory.Write64(vaddr, value); - } +} +void DynarmicCallbacks32::MemoryWrite64(u32 vaddr, u64 value) { + if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { + m_memory.Write64(vaddr, value); } +} - bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override { - return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive8(vaddr, value, expected); - } - bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override { - return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive16(vaddr, value, expected); - } - bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override { - return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive32(vaddr, value, expected); - } - bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override { - return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive64(vaddr, value, expected); - } +bool DynarmicCallbacks32::MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) { + return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive8(vaddr, value, expected); +} +bool DynarmicCallbacks32::MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) { + return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive16(vaddr, value, expected); +} +bool DynarmicCallbacks32::MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) { + return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive32(vaddr, value, expected); +} +bool DynarmicCallbacks32::MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) { + return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive64(vaddr, value, expected); +} - void InterpreterFallback(u32 pc, std::size_t num_instructions) override { - m_parent.LogBacktrace(m_process); - LOG_ERROR(Core_ARM, - "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc, - num_instructions, m_memory.Read32(pc)); - } +void DynarmicCallbacks32::InterpreterFallback(u32 pc, std::size_t num_instructions) { + m_parent.LogBacktrace(m_process); + LOG_ERROR(Core_ARM, + "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc, + num_instructions, m_memory.Read32(pc)); +} - void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - switch (exception) { - case Dynarmic::A32::Exception::NoExecuteFault: - LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc); - ReturnException(pc, PrefetchAbort); +void DynarmicCallbacks32::ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) { + switch (exception) { + case Dynarmic::A32::Exception::NoExecuteFault: + LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc); + ReturnException(pc, PrefetchAbort); + return; + default: + if (m_debugger_enabled) { + ReturnException(pc, InstructionBreakpoint); return; - default: - if (m_debugger_enabled) { - ReturnException(pc, InstructionBreakpoint); - return; - } - - m_parent.LogBacktrace(m_process); - LOG_CRITICAL(Core_ARM, - "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", - exception, pc, m_memory.Read32(pc), m_parent.IsInThumbMode()); } + + m_parent.LogBacktrace(m_process); + LOG_CRITICAL(Core_ARM, + "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", + exception, pc, m_memory.Read32(pc), m_parent.IsInThumbMode()); } +} - void CallSVC(u32 swi) override { - m_parent.m_svc_swi = swi; - m_parent.m_jit->HaltExecution(SupervisorCall); - } +void DynarmicCallbacks32::CallSVC(u32 swi) { + m_parent.m_svc_swi = swi; + m_parent.m_jit->HaltExecution(SupervisorCall); +} - void AddTicks(u64 ticks) override { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); +void DynarmicCallbacks32::AddTicks(u64 ticks) { + ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a - // rough approximation of the amount of executed ticks in the system, it may be thrown off - // if not all cores are doing a similar amount of work. Instead of doing this, we should - // device a way so that timing is consistent across all cores without increasing the ticks 4 - // times. - u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; - // Always execute at least one tick. - amortized_ticks = std::max(amortized_ticks, 1); + // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a + // rough approximation of the amount of executed ticks in the system, it may be thrown off + // if not all cores are doing a similar amount of work. Instead of doing this, we should + // device a way so that timing is consistent across all cores without increasing the ticks 4 + // times. + u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; + // Always execute at least one tick. + amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); - } + m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); +} - u64 GetTicksRemaining() override { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); +u64 DynarmicCallbacks32::GetTicksRemaining() { + ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); - } - - bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { - if (!m_check_memory_access) { - return true; - } - - if (!m_memory.IsValidVirtualAddressRange(addr, size)) { - LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", - addr); - m_parent.m_jit->HaltExecution(PrefetchAbort); - return false; - } - - if (!m_debugger_enabled) { - return true; - } - - const auto match{m_parent.MatchingWatchpoint(addr, size, type)}; - if (match) { - m_parent.m_halted_watchpoint = match; - m_parent.m_jit->HaltExecution(DataAbort); - return false; - } + return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); +} +bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { + if (!m_check_memory_access) { return true; } - void ReturnException(u32 pc, Dynarmic::HaltReason hr) { - m_parent.GetContext(m_parent.m_breakpoint_context); - m_parent.m_breakpoint_context.pc = pc; - m_parent.m_breakpoint_context.r[15] = pc; - m_parent.m_jit->HaltExecution(hr); + if (!m_memory.IsValidVirtualAddressRange(addr, size)) { + LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", + addr); + m_parent.m_jit->HaltExecution(PrefetchAbort); + return false; } - ArmDynarmic32& m_parent; - Core::Memory::Memory& m_memory; - Kernel::KProcess* m_process{}; - const bool m_debugger_enabled{}; - const bool m_check_memory_access{}; -}; + if (!m_debugger_enabled) { + return true; + } -std::shared_ptr ArmDynarmic32::MakeJit(Common::PageTable* page_table) const { + const auto match{m_parent.MatchingWatchpoint(addr, size, type)}; + if (match) { + m_parent.m_halted_watchpoint = match; + m_parent.m_jit->HaltExecution(DataAbort); + return false; + } + + return true; +} + +void DynarmicCallbacks32::ReturnException(u32 pc, Dynarmic::HaltReason hr) { + m_parent.GetContext(m_parent.m_breakpoint_context); + m_parent.m_breakpoint_context.pc = pc; + m_parent.m_breakpoint_context.r[15] = pc; + m_parent.m_jit->HaltExecution(hr); +} + +void ArmDynarmic32::MakeJit(Common::PageTable* page_table) { Dynarmic::A32::UserConfig config; - config.callbacks = m_cb.get(); + config.callbacks = std::addressof(*m_cb); config.coprocessors[15] = m_cp15; config.define_unpredictable_behaviour = true; @@ -315,7 +305,7 @@ std::shared_ptr ArmDynarmic32::MakeJit(Common::PageTable* pa default: break; } - return std::make_unique(config); + m_jit.emplace(config); } static std::pair FpscrToFpsrFpcr(u32 fpscr) { @@ -360,21 +350,17 @@ u32 ArmDynarmic32::GetSvcNumber() const { } void ArmDynarmic32::GetSvcArguments(std::span args) const { - Dynarmic::A32::Jit& j = *m_jit; + Dynarmic::A32::Jit const& j = *m_jit; auto& gpr = j.Regs(); - - for (size_t i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) args[i] = gpr[i]; - } } void ArmDynarmic32::SetSvcArguments(std::span args) { Dynarmic::A32::Jit& j = *m_jit; auto& gpr = j.Regs(); - - for (size_t i = 0; i < 8; i++) { - gpr[i] = static_cast(args[i]); - } + for (size_t i = 0; i < 8; i++) + gpr[i] = u32(args[i]); } const Kernel::DebugWatchpoint* ArmDynarmic32::HaltedWatchpoint() const { @@ -387,11 +373,12 @@ void ArmDynarmic32::RewindBreakpointInstruction() { ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process, DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index) - : ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor}, - m_cb(std::make_unique(*this, process)), - m_cp15(std::make_shared(*this)), m_core_index{core_index} { + : ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor} + , m_cb(std::make_optional(*this, process)) + , m_cp15(std::make_shared(*this)), m_core_index{core_index} +{ auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl(); - m_jit = MakeJit(&page_table_impl); + MakeJit(&page_table_impl); } ArmDynarmic32::~ArmDynarmic32() = default; @@ -401,23 +388,18 @@ void ArmDynarmic32::SetTpidrroEl0(u64 value) { } void ArmDynarmic32::GetContext(Kernel::Svc::ThreadContext& ctx) const { - Dynarmic::A32::Jit& j = *m_jit; + Dynarmic::A32::Jit const& j = *m_jit; auto& gpr = j.Regs(); auto& fpr = j.ExtRegs(); - - for (size_t i = 0; i < 16; i++) { + for (size_t i = 0; i < 16; i++) ctx.r[i] = gpr[i]; - } - ctx.fp = gpr[11]; ctx.sp = gpr[13]; ctx.lr = gpr[14]; ctx.pc = gpr[15]; ctx.pstate = j.Cpsr(); - static_assert(sizeof(fpr) <= sizeof(ctx.v)); std::memcpy(ctx.v.data(), &fpr, sizeof(fpr)); - auto [fpsr, fpcr] = FpscrToFpsrFpcr(j.Fpscr()); ctx.fpcr = fpcr; ctx.fpsr = fpsr; @@ -428,16 +410,11 @@ void ArmDynarmic32::SetContext(const Kernel::Svc::ThreadContext& ctx) { Dynarmic::A32::Jit& j = *m_jit; auto& gpr = j.Regs(); auto& fpr = j.ExtRegs(); - - for (size_t i = 0; i < 16; i++) { - gpr[i] = static_cast(ctx.r[i]); - } - + for (size_t i = 0; i < 16; i++) + gpr[i] = u32(ctx.r[i]); j.SetCpsr(ctx.pstate); - static_assert(sizeof(fpr) <= sizeof(ctx.v)); std::memcpy(&fpr, ctx.v.data(), sizeof(fpr)); - j.SetFpscr(FpsrFpcrToFpscr(ctx.fpsr, ctx.fpcr)); m_cp15->uprw = static_cast(ctx.tpidr); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index b580efe615..1934934bd9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,16 +15,50 @@ namespace Core::Memory { class Memory; } +namespace Kernel { +enum class DebugWatchpointType : u8; +class KPRocess; +} + namespace Core { -class DynarmicCallbacks32; +class ArmDynarmic32; class DynarmicCP15; class System; +class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { +public: + explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process); + u8 MemoryRead8(u32 vaddr) override; + u16 MemoryRead16(u32 vaddr) override; + u32 MemoryRead32(u32 vaddr) override; + u64 MemoryRead64(u32 vaddr) override; + std::optional MemoryReadCode(u32 vaddr) override; + void MemoryWrite8(u32 vaddr, u8 value) override; + void MemoryWrite16(u32 vaddr, u16 value) override; + void MemoryWrite32(u32 vaddr, u32 value) override; + void MemoryWrite64(u32 vaddr, u64 value) override; + bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override; + bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override; + bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override; + bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override; + void InterpreterFallback(u32 pc, std::size_t num_instructions) override; + void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override; + void CallSVC(u32 swi) override; + void AddTicks(u64 ticks) override; + u64 GetTicksRemaining() override; + bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type); + void ReturnException(u32 pc, Dynarmic::HaltReason hr); + ArmDynarmic32& m_parent; + Core::Memory::Memory& m_memory; + Kernel::KProcess* m_process{}; + const bool m_debugger_enabled{}; + const bool m_check_memory_access{}; +}; + class ArmDynarmic32 final : public ArmInterface { public: - ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process, - DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index); + ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process, DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index); ~ArmDynarmic32() override; Architecture GetArchitecture() const override { @@ -57,13 +94,13 @@ private: friend class DynarmicCallbacks32; friend class DynarmicCP15; - std::shared_ptr MakeJit(Common::PageTable* page_table) const; + void MakeJit(Common::PageTable* page_table); - std::unique_ptr m_cb{}; + std::optional m_cb{}; std::shared_ptr m_cp15{}; std::size_t m_core_index{}; - std::shared_ptr m_jit{}; + std::optional m_jit{}; // SVC callback u32 m_svc_swi{}; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index ba6178c1e4..92e1a70458 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -13,223 +13,203 @@ namespace Core { -using Vector = Dynarmic::A64::Vector; using namespace Common::Literals; -class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { -public: - explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process) - : m_parent{parent}, m_memory(process->GetMemory()), - m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()}, - m_check_memory_access{m_debugger_enabled || - !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {} +DynarmicCallbacks64::DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process) + : m_parent{parent}, m_memory(process->GetMemory()) + , m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()} + , m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} +{} - u8 MemoryRead8(u64 vaddr) override { - CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); - return m_memory.Read8(vaddr); - } - u16 MemoryRead16(u64 vaddr) override { - CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); - return m_memory.Read16(vaddr); - } - u32 MemoryRead32(u64 vaddr) override { - CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); - return m_memory.Read32(vaddr); - } - u64 MemoryRead64(u64 vaddr) override { - CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); - return m_memory.Read64(vaddr); - } - Vector MemoryRead128(u64 vaddr) override { - CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read); - return {m_memory.Read64(vaddr), m_memory.Read64(vaddr + 8)}; - } - std::optional MemoryReadCode(u64 vaddr) override { - if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) { - return std::nullopt; - } - return m_memory.Read32(vaddr); - } +u8 DynarmicCallbacks64::MemoryRead8(u64 vaddr) { + CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); + return m_memory.Read8(vaddr); +} +u16 DynarmicCallbacks64::MemoryRead16(u64 vaddr) { + CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); + return m_memory.Read16(vaddr); +} +u32 DynarmicCallbacks64::MemoryRead32(u64 vaddr) { + CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); + return m_memory.Read32(vaddr); +} +u64 DynarmicCallbacks64::MemoryRead64(u64 vaddr) { + CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); + return m_memory.Read64(vaddr); +} +Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) { + CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read); + return {m_memory.Read64(vaddr), m_memory.Read64(vaddr + 8)}; +} +std::optional DynarmicCallbacks64::MemoryReadCode(u64 vaddr) { + if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) + return std::nullopt; + return m_memory.Read32(vaddr); +} - void MemoryWrite8(u64 vaddr, u8 value) override { - if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { - m_memory.Write8(vaddr, value); - } +void DynarmicCallbacks64::MemoryWrite8(u64 vaddr, u8 value) { + if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { + m_memory.Write8(vaddr, value); } - void MemoryWrite16(u64 vaddr, u16 value) override { - if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { - m_memory.Write16(vaddr, value); - } +} +void DynarmicCallbacks64::MemoryWrite16(u64 vaddr, u16 value) { + if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { + m_memory.Write16(vaddr, value); } - void MemoryWrite32(u64 vaddr, u32 value) override { - if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { - m_memory.Write32(vaddr, value); - } +} +void DynarmicCallbacks64::MemoryWrite32(u64 vaddr, u32 value) { + if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { + m_memory.Write32(vaddr, value); } - void MemoryWrite64(u64 vaddr, u64 value) override { - if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { - m_memory.Write64(vaddr, value); - } +} +void DynarmicCallbacks64::MemoryWrite64(u64 vaddr, u64 value) { + if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { + m_memory.Write64(vaddr, value); } - void MemoryWrite128(u64 vaddr, Vector value) override { - if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) { - m_memory.Write64(vaddr, value[0]); - m_memory.Write64(vaddr + 8, value[1]); - } +} +void DynarmicCallbacks64::MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) { + if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) { + m_memory.Write64(vaddr, value[0]); + m_memory.Write64(vaddr + 8, value[1]); } +} - bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override { - return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive8(vaddr, value, expected); - } - bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override { - return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive16(vaddr, value, expected); - } - bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override { - return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive32(vaddr, value, expected); - } - bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override { - return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive64(vaddr, value, expected); - } - bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override { - return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) && - m_memory.WriteExclusive128(vaddr, value, expected); - } +bool DynarmicCallbacks64::MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) { + return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive8(vaddr, value, expected); +} +bool DynarmicCallbacks64::MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) { + return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive16(vaddr, value, expected); +} +bool DynarmicCallbacks64::MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) { + return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive32(vaddr, value, expected); +} +bool DynarmicCallbacks64::MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) { + return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive64(vaddr, value, expected); +} +bool DynarmicCallbacks64::MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vector value, Dynarmic::A64::Vector expected) { + return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) && + m_memory.WriteExclusive128(vaddr, value, expected); +} - void InterpreterFallback(u64 pc, std::size_t num_instructions) override { - m_parent.LogBacktrace(m_process); - LOG_ERROR(Core_ARM, - "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc, - num_instructions, m_memory.Read32(pc)); +void DynarmicCallbacks64::InterpreterFallback(u64 pc, std::size_t num_instructions) { + m_parent.LogBacktrace(m_process); + LOG_ERROR(Core_ARM, "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc, + num_instructions, m_memory.Read32(pc)); + ReturnException(pc, PrefetchAbort); +} + +void DynarmicCallbacks64::InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) { + switch (op) { + case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: { + static constexpr u64 ICACHE_LINE_SIZE = 64; + const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1); + m_parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE); + break; + } + case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU: + m_parent.ClearInstructionCache(); + break; + case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable: + default: + LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op); + break; + } + m_parent.m_jit->HaltExecution(Dynarmic::HaltReason::CacheInvalidation); +} + +void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { + switch (exception) { + case Dynarmic::A64::Exception::WaitForInterrupt: + case Dynarmic::A64::Exception::WaitForEvent: + case Dynarmic::A64::Exception::SendEvent: + case Dynarmic::A64::Exception::SendEventLocal: + case Dynarmic::A64::Exception::Yield: + LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); + return; + case Dynarmic::A64::Exception::NoExecuteFault: + LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc); ReturnException(pc, PrefetchAbort); - } - - void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, - u64 value) override { - switch (op) { - case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: { - static constexpr u64 ICACHE_LINE_SIZE = 64; - - const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1); - m_parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE); - break; - } - case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU: - m_parent.ClearInstructionCache(); - break; - case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable: - default: - LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op); - break; - } - - m_parent.m_jit->HaltExecution(Dynarmic::HaltReason::CacheInvalidation); - } - - void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { - switch (exception) { - case Dynarmic::A64::Exception::WaitForInterrupt: - case Dynarmic::A64::Exception::WaitForEvent: - case Dynarmic::A64::Exception::SendEvent: - case Dynarmic::A64::Exception::SendEventLocal: - case Dynarmic::A64::Exception::Yield: - LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); - return; - case Dynarmic::A64::Exception::NoExecuteFault: - LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc); - ReturnException(pc, PrefetchAbort); - return; - default: - if (m_debugger_enabled) { - ReturnException(pc, InstructionBreakpoint); - } else { - m_parent.LogBacktrace(m_process); - LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); - } + return; + default: + if (m_debugger_enabled) { + ReturnException(pc, InstructionBreakpoint); + } else { + m_parent.LogBacktrace(m_process); + LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); } } +} - void CallSVC(u32 svc) override { - m_parent.m_svc = svc; - m_parent.m_jit->HaltExecution(SupervisorCall); - } +void DynarmicCallbacks64::CallSVC(u32 svc) { + m_parent.m_svc = svc; + m_parent.m_jit->HaltExecution(SupervisorCall); +} - void AddTicks(u64 ticks) override { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); +void DynarmicCallbacks64::AddTicks(u64 ticks) { + ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a - // rough approximation of the amount of executed ticks in the system, it may be thrown off - // if not all cores are doing a similar amount of work. Instead of doing this, we should - // device a way so that timing is consistent across all cores without increasing the ticks 4 - // times. - u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; - // Always execute at least one tick. - amortized_ticks = std::max(amortized_ticks, 1); + // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a + // rough approximation of the amount of executed ticks in the system, it may be thrown off + // if not all cores are doing a similar amount of work. Instead of doing this, we should + // device a way so that timing is consistent across all cores without increasing the ticks 4 + // times. + u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; + // Always execute at least one tick. + amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); - } + m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); +} - u64 GetTicksRemaining() override { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); +u64 DynarmicCallbacks64::GetTicksRemaining() { + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); + return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); +} - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); - } - - u64 GetCNTPCT() override { - return m_parent.m_system.CoreTiming().GetClockTicks(); - } - - bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { - if (!m_check_memory_access) { - return true; - } - - if (!m_memory.IsValidVirtualAddressRange(addr, size)) { - LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", - addr); - m_parent.m_jit->HaltExecution(PrefetchAbort); - return false; - } - - if (!m_debugger_enabled) { - return true; - } - - const auto match{m_parent.MatchingWatchpoint(addr, size, type)}; - if (match) { - m_parent.m_halted_watchpoint = match; - m_parent.m_jit->HaltExecution(DataAbort); - return false; - } +u64 DynarmicCallbacks64::GetCNTPCT() { + return m_parent.m_system.CoreTiming().GetClockTicks(); +} +bool DynarmicCallbacks64::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { + if (!m_check_memory_access) { return true; } - void ReturnException(u64 pc, Dynarmic::HaltReason hr) { - m_parent.GetContext(m_parent.m_breakpoint_context); - m_parent.m_breakpoint_context.pc = pc; - m_parent.m_jit->HaltExecution(hr); + if (!m_memory.IsValidVirtualAddressRange(addr, size)) { + LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", + addr); + m_parent.m_jit->HaltExecution(PrefetchAbort); + return false; } - ArmDynarmic64& m_parent; - Core::Memory::Memory& m_memory; - u64 m_tpidrro_el0{}; - u64 m_tpidr_el0{}; - Kernel::KProcess* m_process{}; - const bool m_debugger_enabled{}; - const bool m_check_memory_access{}; - static constexpr u64 MinimumRunCycles = 10000U; -}; + if (!m_debugger_enabled) { + return true; + } -std::shared_ptr ArmDynarmic64::MakeJit(Common::PageTable* page_table, - std::size_t address_space_bits) const { + const auto match{m_parent.MatchingWatchpoint(addr, size, type)}; + if (match) { + m_parent.m_halted_watchpoint = match; + m_parent.m_jit->HaltExecution(DataAbort); + return false; + } + + return true; +} + +void DynarmicCallbacks64::ReturnException(u64 pc, Dynarmic::HaltReason hr) { + m_parent.GetContext(m_parent.m_breakpoint_context); + m_parent.m_breakpoint_context.pc = pc; + m_parent.m_jit->HaltExecution(hr); +} + +void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_space_bits) { Dynarmic::A64::UserConfig config; // Callbacks - config.callbacks = m_cb.get(); + config.callbacks = std::addressof(*m_cb); // Memory if (page_table) { @@ -375,7 +355,7 @@ std::shared_ptr ArmDynarmic64::MakeJit(Common::PageTable* pa default: break; } - return std::make_shared(config); + m_jit.emplace(config); } HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) { @@ -393,19 +373,15 @@ u32 ArmDynarmic64::GetSvcNumber() const { } void ArmDynarmic64::GetSvcArguments(std::span args) const { - Dynarmic::A64::Jit& j = *m_jit; - - for (size_t i = 0; i < 8; i++) { + Dynarmic::A64::Jit const& j = *m_jit; + for (size_t i = 0; i < 8; i++) args[i] = j.GetRegister(i); - } } void ArmDynarmic64::SetSvcArguments(std::span args) { Dynarmic::A64::Jit& j = *m_jit; - - for (size_t i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) j.SetRegister(i, args[i]); - } } const Kernel::DebugWatchpoint* ArmDynarmic64::HaltedWatchpoint() const { @@ -416,13 +392,14 @@ void ArmDynarmic64::RewindBreakpointInstruction() { this->SetContext(m_breakpoint_context); } -ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process, - DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index) - : ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor}, - m_cb(std::make_unique(*this, process)), m_core_index{core_index} { +ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process, DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index) + : ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor} + , m_cb(std::make_optional(*this, process)) + , m_core_index{core_index} +{ auto& page_table = process->GetPageTable().GetBasePageTable(); auto& page_table_impl = page_table.GetImpl(); - m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth()); + MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth()); } ArmDynarmic64::~ArmDynarmic64() = default; @@ -432,17 +409,14 @@ void ArmDynarmic64::SetTpidrroEl0(u64 value) { } void ArmDynarmic64::GetContext(Kernel::Svc::ThreadContext& ctx) const { - Dynarmic::A64::Jit& j = *m_jit; + Dynarmic::A64::Jit const& j = *m_jit; auto gpr = j.GetRegisters(); auto fpr = j.GetVectors(); - // TODO: this is inconvenient - for (size_t i = 0; i < 29; i++) { + for (size_t i = 0; i < 29; i++) ctx.r[i] = gpr[i]; - } ctx.fp = gpr[29]; ctx.lr = gpr[30]; - ctx.sp = j.GetSP(); ctx.pc = j.GetPC(); ctx.pstate = j.GetPstate(); @@ -454,16 +428,12 @@ void ArmDynarmic64::GetContext(Kernel::Svc::ThreadContext& ctx) const { void ArmDynarmic64::SetContext(const Kernel::Svc::ThreadContext& ctx) { Dynarmic::A64::Jit& j = *m_jit; - // TODO: this is inconvenient std::array gpr; - - for (size_t i = 0; i < 29; i++) { + for (size_t i = 0; i < 29; i++) gpr[i] = ctx.r[i]; - } gpr[29] = ctx.fp; gpr[30] = ctx.lr; - j.SetRegisters(gpr); j.SetSP(ctx.sp); j.SetPC(ctx.pc); diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 08cd982b30..2ea1505ce7 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -17,12 +20,57 @@ namespace Core::Memory { class Memory; } +namespace Kernel { +enum class DebugWatchpointType : u8; +class KPRocess; +} + namespace Core { -class DynarmicCallbacks64; +class ArmDynarmic64; class DynarmicExclusiveMonitor; class System; +class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { +public: + explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process); + + u8 MemoryRead8(u64 vaddr) override; + u16 MemoryRead16(u64 vaddr) override; + u32 MemoryRead32(u64 vaddr) override; + u64 MemoryRead64(u64 vaddr) override; + Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override; + std::optional MemoryReadCode(u64 vaddr) override; + void MemoryWrite8(u64 vaddr, u8 value) override; + void MemoryWrite16(u64 vaddr, u16 value) override; + void MemoryWrite32(u64 vaddr, u32 value) override; + void MemoryWrite64(u64 vaddr, u64 value) override; + void MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) override; + bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override; + bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override; + bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override; + bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override; + bool MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vector value, Dynarmic::A64::Vector expected) override; + void InterpreterFallback(u64 pc, std::size_t num_instructions) override; + void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) override; + void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override; + void CallSVC(u32 svc) override; + void AddTicks(u64 ticks) override; + u64 GetTicksRemaining() override; + u64 GetCNTPCT() override; + bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type); + void ReturnException(u64 pc, Dynarmic::HaltReason hr); + + ArmDynarmic64& m_parent; + Core::Memory::Memory& m_memory; + u64 m_tpidrro_el0{}; + u64 m_tpidr_el0{}; + Kernel::KProcess* m_process{}; + const bool m_debugger_enabled{}; + const bool m_check_memory_access{}; + static constexpr u64 MinimumRunCycles = 10000U; +}; + class ArmDynarmic64 final : public ArmInterface { public: ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process, @@ -59,12 +107,11 @@ private: private: friend class DynarmicCallbacks64; - std::shared_ptr MakeJit(Common::PageTable* page_table, - std::size_t address_space_bits) const; - std::unique_ptr m_cb{}; + void MakeJit(Common::PageTable* page_table, std::size_t address_space_bits); + std::optional m_cb{}; std::size_t m_core_index{}; - std::shared_ptr m_jit{}; + std::optional m_jit{}; // SVC callback u32 m_svc{}; diff --git a/src/core/constants.cpp b/src/core/constants.cpp index 760dc5f238..02768ee537 100644 --- a/src/core/constants.cpp +++ b/src/core/constants.cpp @@ -1,27 +1,332 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/constants.h" namespace Core::Constants { -const std::array ACCOUNT_BACKUP_JPEG{{ - 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, - 0x00, 0x48, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x06, 0x04, 0x04, 0x04, 0x05, 0x04, 0x06, - 0x05, 0x05, 0x06, 0x09, 0x06, 0x05, 0x06, 0x09, 0x0b, 0x08, 0x06, 0x06, 0x08, 0x0b, 0x0c, 0x0a, - 0x0a, 0x0b, 0x0a, 0x0a, 0x0c, 0x10, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x10, 0x0c, 0x0e, 0x0f, - 0x10, 0x0f, 0x0e, 0x0c, 0x13, 0x13, 0x14, 0x14, 0x13, 0x13, 0x1c, 0x1b, 0x1b, 0x1b, 0x1c, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x07, 0x07, - 0x07, 0x0d, 0x0c, 0x0d, 0x18, 0x10, 0x10, 0x18, 0x1a, 0x15, 0x11, 0x15, 0x1a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xff, 0xc0, - 0x00, 0x11, 0x08, 0x00, 0x20, 0x00, 0x20, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, - 0x01, 0xff, 0xc4, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, - 0x14, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, - 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd9, -}}; + const std::array ACCOUNT_BACKUP_JPEG{ + { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, +0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x11, 0x11, 0x11, 0x11, 0x12, 0x11, 0x13, +0x15, 0x15, 0x13, 0x1a, 0x1c, 0x19, 0x1c, 0x1a, 0x26, 0x23, 0x20, 0x20, 0x23, 0x26, 0x3a, 0x2a, +0x2d, 0x2a, 0x2d, 0x2a, 0x3a, 0x58, 0x37, 0x40, 0x37, 0x37, 0x40, 0x37, 0x58, 0x4e, 0x5f, 0x4d, +0x48, 0x4d, 0x5f, 0x4e, 0x8c, 0x6e, 0x62, 0x62, 0x6e, 0x8c, 0xa2, 0x88, 0x81, 0x88, 0xa2, 0xc5, +0xb0, 0xb0, 0xc5, 0xf8, 0xeb, 0xf8, 0xff, 0xff, 0xff, 0x01, 0x11, 0x11, 0x11, 0x11, 0x12, 0x11, +0x13, 0x15, 0x15, 0x13, 0x1a, 0x1c, 0x19, 0x1c, 0x1a, 0x26, 0x23, 0x20, 0x20, 0x23, 0x26, 0x3a, +0x2a, 0x2d, 0x2a, 0x2d, 0x2a, 0x3a, 0x58, 0x37, 0x40, 0x37, 0x37, 0x40, 0x37, 0x58, 0x4e, 0x5f, +0x4d, 0x48, 0x4d, 0x5f, 0x4e, 0x8c, 0x6e, 0x62, 0x62, 0x6e, 0x8c, 0xa2, 0x88, 0x81, 0x88, 0xa2, +0xc5, 0xb0, 0xb0, 0xc5, 0xf8, 0xeb, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x01, +0x00, 0x01, 0x00, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, +0x9a, 0x00, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x05, 0x03, 0x04, 0x06, 0x01, 0x02, 0x10, 0x00, 0x01, 0x03, 0x02, 0x03, 0x05, +0x05, 0x04, 0x08, 0x06, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x11, +0x05, 0x21, 0x53, 0x12, 0x14, 0x15, 0x31, 0x54, 0x13, 0x32, 0x51, 0x71, 0x91, 0x41, 0x52, 0x61, +0xa1, 0x22, 0x33, 0x34, 0x42, 0x72, 0x81, 0xa2, 0xd1, 0x23, 0x43, 0x62, 0x82, 0x92, 0xb1, 0x24, +0xb2, 0x73, 0xe1, 0xf0, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x04, 0x05, 0x02, 0x11, 0x00, 0x02, 0x02, 0x01, +0x03, 0x03, 0x03, 0x04, 0x01, 0x03, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, +0x12, 0x11, 0x13, 0x52, 0x04, 0x21, 0x53, 0x31, 0x91, 0xa1, 0x22, 0x41, 0x51, 0x61, 0x81, 0x05, +0x62, 0x71, 0x32, 0x33, 0x42, 0x43, 0x92, 0xa2, 0xc1, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, +0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x85, 0x56, 0xa2, 0x66, 0xa0, 0x1f, 0x67, +0x84, 0xb9, 0xb1, 0x4a, 0x48, 0xd3, 0x27, 0xed, 0xaf, 0x83, 0x4d, 0x17, 0x63, 0x7e, 0xec, 0x1e, +0xae, 0x25, 0x26, 0x68, 0x87, 0x4b, 0x7c, 0xfd, 0x2b, 0x67, 0x46, 0x0e, 0x4b, 0x8c, 0x56, 0x2f, +0xb8, 0x61, 0x5c, 0x4e, 0xbf, 0x5b, 0xe4, 0xd2, 0x71, 0x65, 0xeb, 0xfa, 0x75, 0xff, 0x00, 0x98, +0x23, 0xb3, 0x07, 0x1b, 0xc4, 0xeb, 0xf5, 0xbe, 0x4d, 0x33, 0x71, 0x8a, 0xbf, 0x08, 0xc6, 0x2c, +0x3f, 0xe9, 0xd7, 0xaf, 0xbc, 0x59, 0xd6, 0x02, 0x45, 0x1e, 0x20, 0xb3, 0x45, 0x2c, 0xb2, 0x33, +0x61, 0xac, 0x3e, 0xe1, 0xc5, 0x29, 0x24, 0x4c, 0xe4, 0xd9, 0xf8, 0x38, 0x8d, 0x19, 0x95, 0xd1, +0x6a, 0x72, 0x58, 0x37, 0x8f, 0xae, 0x85, 0x40, 0x7c, 0x22, 0xb5, 0x53, 0x25, 0x3e, 0xc8, 0x2a, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xe1, 0x22, 0xb7, 0x12, 0x6d, 0x3f, 0xd1, 0x8e, 0xcf, 0x90, 0xd3, +0x8f, 0x1a, 0xd5, 0x87, 0xfc, 0x4f, 0x58, 0x48, 0xd1, 0x1e, 0x96, 0xf9, 0x43, 0x25, 0x0e, 0xc5, +0xc9, 0x9b, 0x2b, 0x98, 0xa9, 0x1b, 0xd1, 0xae, 0xf1, 0xe6, 0x40, 0x9f, 0x0c, 0xae, 0x99, 0x6e, +0xe9, 0x98, 0xf2, 0xa4, 0x78, 0x95, 0x24, 0x9f, 0xcc, 0xb2, 0xf8, 0x38, 0xa1, 0x74, 0xb0, 0xee, +0x84, 0x27, 0x77, 0x4e, 0xff, 0x00, 0xd1, 0xa3, 0xfd, 0xa3, 0x8a, 0x92, 0x8a, 0xae, 0x26, 0xab, +0x9f, 0x03, 0xbe, 0x4a, 0x6b, 0x1d, 0x94, 0x95, 0xb4, 0xb1, 0xa7, 0xd3, 0x9d, 0x86, 0x9b, 0xe9, +0x68, 0xf1, 0x06, 0x3a, 0x48, 0x9d, 0x9f, 0x2d, 0xa3, 0xd2, 0x97, 0xe5, 0x1b, 0xea, 0xeb, 0xa7, +0xa6, 0xb6, 0xd6, 0xd2, 0xe4, 0x73, 0x36, 0x06, 0x79, 0xe9, 0xe5, 0xa7, 0x7e, 0xcb, 0xd3, 0x9f, +0x25, 0xf6, 0x2f, 0x92, 0x98, 0x8b, 0x16, 0x8c, 0xe8, 0xc6, 0x6a, 0x49, 0x34, 0xf5, 0x4c, 0xf9, +0xb0, 0x3e, 0x8c, 0xd4, 0xf1, 0x76, 0xd5, 0x11, 0x46, 0xbe, 0xd7, 0x67, 0xe4, 0x99, 0xa8, 0x6b, +0x44, 0x44, 0xa4, 0xa3, 0x17, 0x27, 0xf6, 0x5a, 0x9b, 0xd5, 0x1f, 0xf1, 0xe8, 0x21, 0x87, 0xef, +0x4b, 0x9b, 0x89, 0x25, 0x0a, 0x84, 0x96, 0xba, 0xa6, 0x47, 0x42, 0xdd, 0xb6, 0xf7, 0x7d, 0x0d, +0xd8, 0x70, 0x5d, 0x69, 0x7f, 0x26, 0x9e, 0x13, 0x49, 0x19, 0x21, 0x75, 0x54, 0xc3, 0xeb, 0x92, +0xca, 0x4f, 0x26, 0x89, 0x10, 0xcf, 0x34, 0x17, 0x58, 0xe4, 0xd9, 0xf1, 0x42, 0xc4, 0x18, 0xce, +0xb3, 0x7f, 0xb9, 0xa7, 0xd4, 0x8c, 0xc2, 0xe8, 0xf2, 0x58, 0xf6, 0xe4, 0xb7, 0x2e, 0x64, 0xb9, +0xab, 0x24, 0x93, 0xe8, 0xb1, 0xad, 0x8a, 0x3f, 0x75, 0xa3, 0x4c, 0xbe, 0xc7, 0x96, 0xab, 0xea, +0xbf, 0xe8, 0x7a, 0x72, 0x7d, 0x8e, 0xbe, 0x39, 0x19, 0x2b, 0x76, 0x98, 0xe4, 0x73, 0x54, 0xcc, +0x70, 0xb0, 0x4f, 0x35, 0x3b, 0xb6, 0xa3, 0x75, 0xaf, 0xcd, 0x39, 0xa1, 0xd2, 0xd2, 0x62, 0x31, +0x54, 0x7d, 0x17, 0x7d, 0x17, 0xf8, 0x1e, 0x65, 0x06, 0x8c, 0x37, 0xf4, 0x73, 0xab, 0xbc, 0x7e, +0xa8, 0x95, 0x40, 0x07, 0x83, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1e, 0x1a, 0x33, 0xa3, 0xa5, 0x66, 0xcc, 0x75, 0x0d, 0x62, 0xaf, 0xb7, 0x9a, +0xd8, 0x97, 0x88, 0xd7, 0xf3, 0x86, 0x25, 0xfc, 0x4a, 0x43, 0x2e, 0x8d, 0x4f, 0xd4, 0xdf, 0x47, +0x45, 0x29, 0x25, 0x37, 0x2c, 0x7f, 0x0b, 0x42, 0xcf, 0x08, 0x6f, 0x54, 0x9e, 0x83, 0x84, 0x37, +0xaa, 0x42, 0x30, 0x2d, 0xc2, 0x5c, 0x8d, 0xdb, 0x77, 0x79, 0xdf, 0xfe, 0x25, 0x9e, 0x10, 0xde, +0xa9, 0x07, 0x08, 0x6f, 0x54, 0x46, 0x07, 0x9c, 0x3f, 0xb8, 0x8d, 0xbb, 0xbc, 0xff, 0x00, 0xfa, +0xa2, 0xc3, 0xb0, 0x9c, 0x96, 0xd5, 0x0d, 0x28, 0xd3, 0xd4, 0x44, 0xcd, 0x88, 0x1c, 0xd4, 0x8d, +0xd6, 0xb3, 0x52, 0xf7, 0x45, 0xf2, 0x53, 0x99, 0x48, 0xe4, 0x77, 0x26, 0x3d, 0x7f, 0x25, 0x3d, +0x58, 0x66, 0x4f, 0xe5, 0xbf, 0xc3, 0x92, 0x90, 0xe0, 0x9f, 0xfc, 0x8f, 0x13, 0xa1, 0xda, 0xb1, +0xb2, 0xed, 0x4e, 0xca, 0x68, 0x63, 0x9d, 0x8a, 0xc9, 0x1b, 0x76, 0x9c, 0xe3, 0xb0, 0xe4, 0x64, +0xbb, 0x0f, 0x9b, 0x65, 0xae, 0xee, 0x38, 0xcf, 0x41, 0x89, 0xab, 0x7f, 0x87, 0x3a, 0xdd, 0x3d, +0x8f, 0x2f, 0x4b, 0x14, 0x73, 0x46, 0xe6, 0x3d, 0x2e, 0xd5, 0x2b, 0xef, 0x07, 0xa1, 0x8b, 0x2b, +0xba, 0x59, 0x38, 0x39, 0x3c, 0x59, 0xcd, 0x4d, 0x87, 0xc7, 0x0a, 0x7d, 0xa7, 0x6d, 0xdc, 0x91, +0xac, 0x65, 0xd4, 0xa5, 0x86, 0xd0, 0x3a, 0x9d, 0x56, 0x49, 0x3b, 0xca, 0x86, 0xd5, 0x25, 0x0c, +0x34, 0xad, 0xcb, 0x37, 0xdb, 0x37, 0xa9, 0x96, 0xa6, 0xa1, 0x94, 0xd1, 0x2b, 0xdc, 0x43, 0x93, +0x7d, 0x84, 0xfa, 0x8b, 0x6c, 0xd6, 0xa8, 0xc9, 0xcb, 0x5f, 0xbe, 0x87, 0xd4, 0xb2, 0xc3, 0x03, +0x36, 0x9e, 0xe4, 0x6a, 0x21, 0x39, 0xcf, 0xa9, 0xaa, 0xc9, 0xaf, 0xec, 0x19, 0xf9, 0x2b, 0xd7, +0xf6, 0x39, 0xf9, 0xe7, 0x96, 0x77, 0xed, 0xbd, 0xdf, 0xb2, 0x27, 0x82, 0x18, 0x4b, 0x15, 0x5d, +0xbb, 0xbe, 0xe6, 0x9a, 0xfa, 0x1d, 0x12, 0x6e, 0x4b, 0x2f, 0xf1, 0xa9, 0x6b, 0x84, 0x37, 0xaa, +0x41, 0xc2, 0x1b, 0xd5, 0x21, 0x18, 0x1e, 0xf1, 0x7c, 0xcb, 0xf6, 0xef, 0xf3, 0xfc, 0x22, 0xcf, +0x08, 0x6f, 0x54, 0x87, 0xbc, 0x29, 0xbd, 0x51, 0x14, 0x13, 0x84, 0xb9, 0x0d, 0xab, 0xfc, 0xff, +0x00, 0x07, 0x61, 0x05, 0xe2, 0x8d, 0x1b, 0x24, 0xed, 0x7d, 0xbe, 0xf7, 0x23, 0x78, 0xe0, 0x4b, +0xf8, 0x75, 0x7a, 0xb9, 0x52, 0x19, 0x17, 0xf0, 0xb8, 0xaa, 0x75, 0x35, 0xdc, 0xc3, 0x7f, 0x47, +0x28, 0x27, 0x35, 0x2c, 0xbf, 0x27, 0x40, 0x00, 0x29, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1e, 0x12, 0x31, 0x2a, 0xcd, 0xdd, 0x9b, 0x0c, 0xef, 0xb8, 0xa3, 0x34, 0xac, +0x85, 0x8e, 0x7b, 0xd7, 0x24, 0x42, 0x0c, 0x98, 0x85, 0x14, 0x8b, 0xb4, 0xfa, 0x65, 0x52, 0xca, +0xe2, 0xdb, 0xd7, 0x16, 0xd2, 0x2f, 0xa2, 0x0e, 0x52, 0x52, 0xc1, 0xc9, 0x22, 0x20, 0x2c, 0x6f, +0xb8, 0x7f, 0x48, 0x66, 0x8d, 0x22, 0xac, 0xba, 0x41, 0x48, 0xd6, 0x26, 0xa3, 0x9a, 0x86, 0x97, +0x3d, 0x3d, 0x60, 0xd1, 0xd2, 0x7d, 0x44, 0xa3, 0xdd, 0xd6, 0xd2, 0xfd, 0xb4, 0x43, 0x6b, 0x5c, +0xf7, 0x23, 0x5a, 0xd5, 0x72, 0xf8, 0x26, 0x6a, 0x57, 0xa7, 0xc2, 0x1e, 0xeb, 0x3a, 0x67, 0x6c, +0xff, 0x00, 0x4a, 0x16, 0xa0, 0xa5, 0x82, 0x95, 0xbf, 0x41, 0x3c, 0xd4, 0xf8, 0x7e, 0x23, 0x48, +0xce, 0x73, 0x37, 0xf2, 0x2a, 0x95, 0xb2, 0x97, 0x68, 0xa3, 0x34, 0xfa, 0xbb, 0x66, 0xf4, 0xaa, +0x2c, 0xf6, 0x3a, 0x0a, 0x58, 0xb9, 0x44, 0xdf, 0x35, 0x37, 0x11, 0xad, 0x4e, 0x48, 0x84, 0x67, +0xe3, 0x30, 0xa2, 0x2e, 0xc4, 0x6e, 0x71, 0xaa, 0xfc, 0x6a, 0x5f, 0xbb, 0x13, 0x4f, 0x1b, 0x56, +0x4b, 0xec, 0x51, 0xb1, 0xd4, 0xcd, 0xeb, 0x2d, 0x7f, 0x96, 0x74, 0xa0, 0xe7, 0x61, 0xa8, 0xaf, +0xab, 0xb7, 0x65, 0x22, 0x31, 0xa8, 0xb6, 0x73, 0xb6, 0x3f, 0x72, 0xdc, 0x4d, 0x7b, 0x18, 0x8d, +0x7b, 0xf6, 0xdc, 0x9c, 0xd7, 0x91, 0xe2, 0x51, 0x71, 0xf5, 0x65, 0x56, 0x56, 0xeb, 0xec, 0xe4, +0xb5, 0xfc, 0x23, 0x2e, 0xcb, 0x57, 0x9a, 0x1f, 0x47, 0xa0, 0xf2, 0x56, 0x09, 0x95, 0x54, 0x0d, +0xaa, 0x7b, 0x1c, 0xf9, 0x1d, 0xb2, 0xd4, 0x5c, 0x90, 0xa6, 0x6b, 0xcf, 0xda, 0xac, 0x6b, 0xd9, +0x2b, 0x51, 0xfe, 0xcb, 0xe6, 0x84, 0xa7, 0xa3, 0x3d, 0x42, 0x52, 0x8c, 0x93, 0x8b, 0xd1, 0xfe, +0x4d, 0x66, 0x61, 0xd4, 0x6c, 0xe5, 0x12, 0x2f, 0x99, 0xb0, 0xd8, 0x61, 0x67, 0x76, 0x26, 0x27, +0x92, 0x21, 0x16, 0x45, 0xab, 0x8f, 0xed, 0x2b, 0x3d, 0xbd, 0xe8, 0xd5, 0x2c, 0xbe, 0x86, 0x0d, +0xf2, 0x8f, 0xdd, 0xa8, 0xff, 0x00, 0x35, 0x2c, 0xc2, 0x52, 0xfb, 0xea, 0x69, 0xdb, 0xb6, 0x7d, +0xf3, 0x72, 0xff, 0x00, 0x0f, 0x53, 0xa4, 0xec, 0xe3, 0xf7, 0x1b, 0xe8, 0x6b, 0x3e, 0x82, 0x91, +0xe9, 0x65, 0x81, 0x9f, 0x96, 0x44, 0x4d, 0xf2, 0x0e, 0x48, 0xb5, 0x29, 0xe5, 0x22, 0x8e, 0x27, +0x2b, 0x6d, 0xb1, 0x2b, 0xd7, 0xf1, 0xb1, 0xab, 0xf3, 0x45, 0x43, 0xd2, 0xaa, 0xcf, 0xb6, 0xa1, +0x51, 0x7a, 0x7d, 0xa4, 0xd1, 0xb3, 0x36, 0x0f, 0x95, 0xe1, 0x7d, 0xbe, 0x0e, 0x22, 0xcb, 0x0c, +0xd0, 0xbb, 0x66, 0x48, 0xf6, 0x7c, 0x3c, 0x17, 0xc9, 0x4b, 0x51, 0x63, 0x3a, 0x91, 0x7a, 0x1b, +0x8d, 0xad, 0xa1, 0xa8, 0x62, 0xb5, 0xce, 0x6f, 0xc5, 0xaf, 0x25, 0x3b, 0x61, 0xeb, 0x16, 0xd1, +0xa2, 0x37, 0x75, 0x55, 0x7f, 0xb9, 0x07, 0x28, 0x9c, 0xa1, 0xed, 0xd5, 0x2d, 0x9f, 0x2c, 0xd1, +0x4e, 0x81, 0xd4, 0xad, 0xa7, 0xfa, 0x6d, 0x81, 0xb3, 0x45, 0xe1, 0xf7, 0x93, 0xf7, 0x43, 0x5f, +0x7d, 0xc3, 0xba, 0x42, 0xd5, 0x3c, 0xbd, 0x22, 0xd9, 0x72, 0xea, 0x33, 0xd7, 0x1a, 0xdc, 0x91, +0x4f, 0x0e, 0xac, 0x4a, 0x98, 0xec, 0xee, 0xfb, 0x51, 0x2e, 0x53, 0x39, 0xc8, 0xb1, 0x0a, 0x18, +0xd6, 0xec, 0xa6, 0x56, 0xa9, 0x7d, 0x8f, 0x6c, 0x8c, 0x6b, 0xdb, 0xc9, 0x53, 0x23, 0x35, 0x91, +0x69, 0xeb, 0x8b, 0x49, 0x9c, 0xcb, 0xe0, 0xe3, 0x2c, 0xb0, 0x71, 0x4c, 0xca, 0x00, 0x2b, 0x29, +0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe2, 0xad, 0x91, 0x40, 0x39, 0xec, 0x62, 0xa7, 0xbb, 0x03, +0x7c, 0xdc, 0x41, 0x28, 0xcd, 0x47, 0x5b, 0x34, 0xaf, 0x7a, 0xc1, 0xcd, 0xca, 0x63, 0xdc, 0x2a, +0xf4, 0x1c, 0x74, 0x6b, 0xdb, 0x8c, 0x12, 0xc9, 0x1d, 0x6a, 0x5d, 0x75, 0xd6, 0xa3, 0x9c, 0x4d, +0x68, 0xd6, 0x34, 0x75, 0xdf, 0x75, 0x6f, 0x3b, 0x26, 0x57, 0xf8, 0x5f, 0xd8, 0x50, 0x7e, 0x2b, +0x36, 0xce, 0xcc, 0x4d, 0x6c, 0x6d, 0x43, 0x5f, 0x70, 0xac, 0xd0, 0x78, 0xe1, 0xf5, 0x9a, 0x0e, +0x25, 0xaa, 0xa4, 0xf5, 0x72, 0x4c, 0xf5, 0x29, 0x53, 0x26, 0x9c, 0x9c, 0x5f, 0xf2, 0x60, 0x7c, +0xd2, 0xcb, 0x9b, 0xe4, 0x73, 0xb3, 0xbf, 0x35, 0x5b, 0x18, 0x8d, 0xdd, 0xc2, 0xaf, 0x41, 0xc3, +0x70, 0xac, 0xd0, 0x71, 0xeb, 0x5a, 0x97, 0xa4, 0xa2, 0x7a, 0x56, 0xd4, 0xbd, 0x25, 0x13, 0x48, +0xa7, 0x41, 0x40, 0xb5, 0x0e, 0xdb, 0x7f, 0xd5, 0xa2, 0xfa, 0xa9, 0x86, 0x0a, 0x19, 0x5f, 0x3b, +0x63, 0x7b, 0x76, 0x72, 0xbb, 0xbc, 0x8e, 0xb9, 0x91, 0xb6, 0x36, 0x23, 0x18, 0x96, 0x6a, 0x26, +0x48, 0x51, 0x75, 0xaa, 0x2b, 0x18, 0xbe, 0xec, 0xcf, 0xd4, 0x75, 0x38, 0xac, 0x60, 0xfb, 0xbf, +0xb8, 0x63, 0x1b, 0x1b, 0x51, 0xad, 0x4b, 0x22, 0x26, 0x48, 0x64, 0x31, 0xb9, 0xed, 0x63, 0x55, +0xce, 0x5c, 0x91, 0x33, 0xf2, 0x24, 0xcf, 0x55, 0x55, 0x2a, 0x5a, 0x96, 0x27, 0x6c, 0xea, 0x7e, +0xc8, 0xa6, 0x48, 0xc5, 0xcd, 0xff, 0x00, 0xf5, 0x98, 0x23, 0x09, 0x4d, 0xfa, 0xff, 0x00, 0x2c, +0xa1, 0x35, 0x54, 0x34, 0xed, 0xbc, 0x8f, 0x24, 0x3b, 0x15, 0x9a, 0x49, 0x12, 0x3a, 0x78, 0xbf, +0xc8, 0x9e, 0xb4, 0x35, 0xae, 0x55, 0x73, 0xa2, 0x7f, 0xad, 0xd4, 0xdd, 0xc2, 0xa9, 0x97, 0xb7, +0x92, 0x47, 0x27, 0xd5, 0xdd, 0xa6, 0x8d, 0xba, 0xe1, 0x16, 0xdb, 0x52, 0x66, 0xc5, 0x5d, 0x15, +0xc1, 0xc9, 0xb5, 0x39, 0x1b, 0x58, 0x85, 0x6c, 0x94, 0xbd, 0x93, 0x59, 0xb2, 0xae, 0x5e, 0xf0, +0xa6, 0xc5, 0xa3, 0x7d, 0x9b, 0x32, 0x6c, 0x38, 0x9f, 0x59, 0x14, 0xf5, 0x35, 0x53, 0x3a, 0x38, +0xd5, 0xcd, 0x6a, 0xa3, 0x7d, 0x10, 0xd6, 0xdc, 0x2b, 0x34, 0x14, 0xf5, 0x1a, 0xaa, 0x70, 0x5a, +0xb4, 0x99, 0x31, 0xaa, 0x87, 0x52, 0x52, 0x69, 0x48, 0xec, 0x91, 0x51, 0x51, 0x33, 0xbd, 0xc9, +0xb5, 0x38, 0x6c, 0x13, 0xe7, 0x6d, 0x87, 0x78, 0xa1, 0x3a, 0x91, 0xd5, 0xd4, 0x79, 0x3a, 0x17, +0xba, 0x22, 0xfc, 0x52, 0xb2, 0x56, 0x23, 0xd8, 0xb7, 0x6a, 0xa6, 0x4a, 0x67, 0x69, 0xd7, 0x2d, +0x53, 0xd7, 0xf6, 0x8c, 0xad, 0x4e, 0x99, 0x6b, 0x09, 0xff, 0x00, 0x28, 0xe3, 0x6a, 0x69, 0xa4, +0xa6, 0x7d, 0x9e, 0x9e, 0x4b, 0xec, 0x54, 0x35, 0xce, 0xe2, 0x78, 0x99, 0x3b, 0x1d, 0x1b, 0xf9, +0x2a, 0x1c, 0x9c, 0x94, 0x53, 0xb2, 0x67, 0x44, 0xd4, 0xbd, 0x93, 0x2f, 0x23, 0x5d, 0x36, 0xa9, +0x2d, 0x25, 0xd9, 0xa3, 0x75, 0x1d, 0x4a, 0x9a, 0x79, 0x68, 0xa4, 0x8d, 0x30, 0x6e, 0xf0, 0xfa, +0xcd, 0x07, 0x0e, 0x1f, 0x59, 0xa0, 0xe2, 0xdc, 0xab, 0xe4, 0x8b, 0xf7, 0x6b, 0xe6, 0x8c, 0x11, +0x4f, 0x2c, 0x59, 0xc7, 0x23, 0x9b, 0x9d, 0xfe, 0x1f, 0x9a, 0x72, 0x53, 0xe6, 0x49, 0x16, 0x47, +0x6d, 0x2a, 0x25, 0xd7, 0x9d, 0xb2, 0xba, 0xf8, 0xd8, 0xd9, 0xdc, 0x2b, 0x34, 0x1c, 0x38, 0x7d, +0x66, 0x82, 0x91, 0xad, 0x49, 0xeb, 0x94, 0x48, 0x56, 0x52, 0x9e, 0x4a, 0x51, 0xd4, 0xd2, 0x2f, +0xe0, 0xf3, 0xae, 0xcb, 0xa0, 0x77, 0x9b, 0x49, 0xbc, 0x3e, 0xb3, 0x45, 0xc6, 0x6a, 0x6a, 0x5a, +0xc8, 0x67, 0x8d, 0xfd, 0x8b, 0xac, 0x8a, 0x79, 0xb7, 0x09, 0xc1, 0xac, 0x91, 0x5d, 0xd2, 0xae, +0xca, 0xda, 0xc9, 0x1d, 0x68, 0x3c, 0x3d, 0x39, 0xc7, 0x24, 0x00, 0x00, 0x00, 0x00, 0x01, 0x37, +0x11, 0x9d, 0xd4, 0xf4, 0xea, 0xe6, 0x77, 0x95, 0x51, 0x10, 0xa4, 0x73, 0xb8, 0xd3, 0xf3, 0x86, +0x3f, 0x35, 0x2d, 0xa2, 0x39, 0xdb, 0x14, 0x59, 0x4c, 0x72, 0xb2, 0x28, 0x9f, 0xc4, 0x6b, 0x75, +0x87, 0x10, 0xac, 0xd6, 0x34, 0x8f, 0x4e, 0xa6, 0xd4, 0x38, 0x23, 0xa7, 0x85, 0x7c, 0x11, 0xb9, +0xc4, 0x2b, 0x35, 0x9c, 0x38, 0x85, 0x5e, 0xb3, 0x8d, 0x20, 0x36, 0xab, 0xe0, 0x88, 0xc2, 0xbe, +0x11, 0x37, 0x78, 0x85, 0x66, 0xb0, 0xe2, 0x15, 0x9a, 0xc6, 0x90, 0x1b, 0x55, 0xf0, 0x43, 0x0a, +0xf8, 0x23, 0xa9, 0xc3, 0x1a, 0xee, 0xc5, 0x66, 0x93, 0xbf, 0x22, 0xdd, 0x57, 0xc8, 0xa4, 0xf7, +0xb5, 0x8d, 0x57, 0x39, 0x6c, 0x88, 0x99, 0xa9, 0x8a, 0x9d, 0x89, 0x1c, 0x11, 0xb7, 0xc1, 0x88, +0x87, 0x3b, 0x88, 0xd6, 0xf6, 0xee, 0xec, 0x99, 0xf5, 0x7f, 0xf6, 0x53, 0x9b, 0x1a, 0xdd, 0xd6, +0xbd, 0x3d, 0x35, 0x30, 0x28, 0x3b, 0x6d, 0x7f, 0x8d, 0x4f, 0x8a, 0xda, 0xf7, 0x4e, 0xeb, 0x33, +0xea, 0xda, 0xbe, 0xa6, 0x24, 0xaf, 0xac, 0xd6, 0x53, 0x4c, 0x1d, 0x15, 0x4c, 0x12, 0x4b, 0x14, +0x6f, 0x50, 0xae, 0x31, 0x4b, 0x14, 0x6e, 0x6f, 0xf5, 0x9a, 0xca, 0x74, 0xb4, 0x71, 0xac, 0x34, +0xac, 0x47, 0x77, 0xad, 0xb4, 0xef, 0x35, 0xcd, 0x4e, 0x5e, 0x92, 0x34, 0x9a, 0xa2, 0x16, 0x78, +0xbb, 0xe4, 0x99, 0xa9, 0xd5, 0x56, 0x3b, 0x62, 0x9a, 0x55, 0xf6, 0xec, 0x2a, 0x27, 0x9a, 0xe4, +0x86, 0x4e, 0xa5, 0x45, 0x4a, 0x10, 0x8a, 0x4b, 0x5f, 0x53, 0x2f, 0x51, 0x8e, 0xb1, 0x84, 0x52, +0x5a, 0xfa, 0x9c, 0xba, 0x56, 0x54, 0x31, 0x5e, 0xac, 0x92, 0xdb, 0x4f, 0x57, 0x2f, 0x2e, 0x6a, +0x7d, 0x6f, 0xf5, 0x9a, 0xca, 0x69, 0x03, 0x66, 0xd4, 0x38, 0xa3, 0x5e, 0x15, 0xf1, 0x46, 0xee, +0xff, 0x00, 0x57, 0xae, 0xa6, 0x5a, 0x2a, 0xf7, 0x41, 0x22, 0xf6, 0x9d, 0xc7, 0x2e, 0x64, 0xd0, +0x1d, 0x35, 0xb8, 0xb5, 0x8a, 0x21, 0xc2, 0xb7, 0x16, 0xb1, 0x5d, 0xce, 0xe9, 0xae, 0x6b, 0xda, +0x8e, 0x6a, 0xdd, 0x15, 0x09, 0xb8, 0x9a, 0x39, 0xb1, 0x24, 0xd1, 0xad, 0x9f, 0x19, 0x3b, 0x0d, +0xad, 0x48, 0x7f, 0x84, 0xf5, 0xb3, 0x17, 0x92, 0xfc, 0x54, 0xb5, 0x5c, 0x88, 0xea, 0x49, 0xff, +0x00, 0x02, 0xfc, 0x8e, 0x73, 0xad, 0xd3, 0x74, 0x53, 0xf4, 0xd4, 0xc1, 0x83, 0xaa, 0xd5, 0xf8, +0xd4, 0xe6, 0xf8, 0x85, 0x66, 0xb0, 0xe2, 0x15, 0x9a, 0xc6, 0x90, 0x3a, 0x3b, 0x55, 0xf0, 0x46, +0xfc, 0x2b, 0xe0, 0x8d, 0xde, 0x21, 0x59, 0xac, 0x38, 0x85, 0x66, 0xb1, 0xa4, 0x06, 0xd4, 0x38, +0x22, 0x70, 0xaf, 0x82, 0x37, 0x78, 0x85, 0x66, 0xb0, 0xe2, 0x15, 0x9a, 0xc6, 0x90, 0x1b, 0x55, +0xf0, 0x43, 0x1a, 0xf8, 0x23, 0xaf, 0xa0, 0x99, 0x67, 0xa6, 0x63, 0xdd, 0xde, 0xe4, 0xbe, 0x68, +0x6f, 0x90, 0x30, 0x57, 0xdd, 0x26, 0x8f, 0xc1, 0x51, 0x4b, 0xe7, 0x2a, 0xe8, 0xe1, 0x64, 0x91, +0xcc, 0xb6, 0x38, 0xd9, 0x24, 0x7a, 0x00, 0x2b, 0x2b, 0x00, 0x00, 0x0f, 0x08, 0xb5, 0xb4, 0xf4, +0xb2, 0xcb, 0xb5, 0x25, 0x46, 0xc3, 0x91, 0x0b, 0x67, 0x21, 0x88, 0xfd, 0xb6, 0x6f, 0x34, 0xff, +0x00, 0xaa, 0x1a, 0x3a, 0x58, 0xb9, 0x59, 0xda, 0x4d, 0x76, 0x2e, 0xa5, 0x6b, 0x3f, 0x5d, 0x0d, +0x8d, 0xca, 0x83, 0xac, 0x6f, 0xaa, 0x0d, 0xc6, 0x83, 0xad, 0x4f, 0x54, 0x24, 0x03, 0xa1, 0xb3, +0x67, 0x99, 0x9a, 0xb1, 0x97, 0x91, 0x95, 0xf7, 0x2a, 0x0e, 0xb1, 0x3d, 0x50, 0x6e, 0x34, 0x1d, +0x6b, 0x7d, 0x50, 0x90, 0x06, 0xcd, 0x9e, 0x69, 0x0c, 0x65, 0xcd, 0x95, 0xf7, 0x2a, 0x1e, 0xad, +0x0c, 0x15, 0x14, 0xb4, 0xb1, 0xc6, 0xe7, 0x47, 0x53, 0xb6, 0xec, 0xac, 0xdc, 0x89, 0xe0, 0x9d, +0xa9, 0xf9, 0x58, 0xc5, 0xf3, 0x67, 0x49, 0x5b, 0x32, 0x2a, 0xa5, 0x32, 0x4a, 0xd6, 0x5d, 0x2f, +0x23, 0xbc, 0x1a, 0x68, 0xee, 0x54, 0x3d, 0x63, 0x49, 0x6e, 0x7b, 0x9e, 0xe7, 0x39, 0xcb, 0x75, +0x55, 0xcc, 0xf9, 0x3c, 0x43, 0xa7, 0x70, 0x49, 0x2b, 0x1a, 0xfc, 0x91, 0x1a, 0xf1, 0x5d, 0xa7, +0xa1, 0x5f, 0x72, 0xa0, 0xeb, 0x1b, 0xea, 0x83, 0x71, 0xa1, 0xeb, 0x5b, 0xea, 0x84, 0x80, 0x7a, +0xda, 0xb3, 0xcc, 0xc9, 0xc6, 0x5e, 0x46, 0x5f, 0xc3, 0x69, 0xe2, 0x65, 0x44, 0xce, 0x63, 0xf6, +0xda, 0xd6, 0xa2, 0x23, 0xbe, 0x2a, 0x6f, 0x62, 0x5f, 0x64, 0x7f, 0xc5, 0x5a, 0x62, 0xc2, 0x59, +0xb3, 0x48, 0x8b, 0xef, 0x39, 0x54, 0xf8, 0xc6, 0x7e, 0xce, 0xcf, 0xfc, 0x88, 0x60, 0xef, 0x2e, +0xa5, 0x2d, 0x7d, 0x1a, 0x5e, 0xc6, 0x57, 0xf5, 0x5c, 0xbb, 0xfd, 0xcd, 0x1d, 0xca, 0x83, 0xac, +0x6f, 0xaa, 0x0d, 0xca, 0x83, 0xac, 0x6f, 0xaa, 0x12, 0x01, 0xbf, 0x66, 0xcf, 0x33, 0x35, 0x62, +0xfc, 0x8c, 0xaf, 0xb9, 0x50, 0xf5, 0x88, 0x37, 0x1a, 0x0e, 0xb5, 0xbe, 0xa8, 0x48, 0x03, 0x6a, +0xcf, 0x33, 0xf6, 0x43, 0x19, 0x79, 0x19, 0x5b, 0x72, 0xa1, 0xeb, 0x1a, 0x64, 0x75, 0x52, 0x32, +0x9a, 0xa2, 0x07, 0x4b, 0xb6, 0xa8, 0xcb, 0x31, 0xc9, 0x6c, 0xd1, 0x48, 0xa0, 0x87, 0x43, 0x93, +0x59, 0x4d, 0xbd, 0x06, 0x1a, 0xe9, 0x94, 0x9b, 0xd0, 0xae, 0x94, 0x34, 0x3d, 0x6b, 0x7d, 0x50, +0xf7, 0x72, 0xa0, 0xeb, 0x1b, 0xea, 0x84, 0x70, 0x4e, 0xcd, 0x9e, 0x66, 0x31, 0x97, 0x91, 0x96, +0x37, 0x1a, 0x0e, 0xb1, 0xbe, 0xa8, 0x79, 0xb8, 0xd0, 0x75, 0x89, 0xfe, 0x48, 0x48, 0x03, 0x66, +0xcf, 0x33, 0xf6, 0x18, 0xcb, 0xc8, 0xca, 0xfb, 0x8d, 0x07, 0x5a, 0xdf, 0x54, 0x1b, 0x8d, 0x07, +0x58, 0xdf, 0x54, 0x24, 0x01, 0xb3, 0x67, 0x99, 0xfb, 0x0c, 0x65, 0xe4, 0x67, 0x4b, 0x41, 0x0d, +0x34, 0x32, 0x3b, 0xb2, 0x9f, 0x6d, 0x55, 0xb9, 0xa1, 0x64, 0xe5, 0x30, 0x9f, 0xb5, 0xa7, 0xe0, +0x3a, 0xb3, 0x9d, 0xd4, 0xc5, 0xc6, 0xde, 0xf2, 0xd7, 0xb1, 0x92, 0xe5, 0xa4, 0xfd, 0x75, 0xec, +0x00, 0x05, 0x05, 0x40, 0x00, 0x00, 0x39, 0x0c, 0x46, 0xfb, 0xe4, 0x87, 0x5e, 0x47, 0xac, 0xac, +0x6c, 0x12, 0xa3, 0x5d, 0x0e, 0xd6, 0x48, 0xb7, 0x34, 0x74, 0xd2, 0x71, 0xb3, 0xb4, 0x75, 0x7a, +0x16, 0x56, 0xda, 0x91, 0xcd, 0xd8, 0x58, 0xb5, 0xc4, 0xe2, 0xe9, 0x90, 0x71, 0x38, 0xba, 0x64, +0x37, 0xee, 0xdb, 0xe1, 0x7e, 0xe6, 0x8d, 0xc9, 0x71, 0x22, 0x58, 0xf4, 0xb3, 0xc5, 0x22, 0xe9, +0x90, 0x71, 0x38, 0xfa, 0x56, 0x93, 0xbb, 0x6f, 0x87, 0xe4, 0x6e, 0x4b, 0x89, 0x16, 0xc2, 0xc5, +0xae, 0x27, 0x1f, 0x4c, 0xd1, 0xc4, 0xe2, 0xe9, 0x9a, 0x37, 0x6d, 0xf0, 0xfc, 0x8d, 0xc9, 0x71, +0x23, 0x58, 0xf2, 0xc5, 0xae, 0x29, 0x17, 0x4c, 0x83, 0x89, 0xc5, 0xd3, 0x20, 0xdd, 0xb7, 0xc2, +0xfd, 0xc6, 0xe4, 0xb8, 0x91, 0x6c, 0x2c, 0x5a, 0xe2, 0x71, 0xf4, 0xcd, 0x30, 0x4f, 0x5a, 0xd9, +0xd8, 0xb1, 0xa5, 0x3b, 0x5b, 0xb5, 0x64, 0x47, 0x7b, 0x51, 0x6e, 0x37, 0x6d, 0xfb, 0xd5, 0xf2, +0x46, 0xe4, 0xb8, 0x9d, 0x05, 0x2b, 0x76, 0x29, 0xe2, 0x6f, 0x83, 0x10, 0x99, 0x8c, 0xaf, 0xf0, +0xe2, 0x4f, 0xea, 0x52, 0xda, 0x25, 0x91, 0x08, 0xf8, 0x94, 0xa9, 0x14, 0x94, 0xcf, 0x58, 0xd1, +0xf6, 0x57, 0x9c, 0xea, 0x5b, 0xde, 0x4f, 0x4d, 0x5f, 0x76, 0x67, 0x83, 0xfa, 0xd3, 0x39, 0xc3, +0xdb, 0x16, 0xb8, 0x9c, 0x5d, 0x32, 0x0e, 0x27, 0x17, 0x4c, 0xd3, 0xa1, 0xbb, 0x6f, 0x85, 0xfb, +0x9a, 0x77, 0x25, 0xc4, 0x8b, 0x61, 0x62, 0xcf, 0x14, 0x67, 0x4a, 0xd1, 0xc4, 0xd9, 0xd2, 0xb4, +0x6e, 0xdb, 0xe1, 0xf9, 0x1b, 0x92, 0xe2, 0x45, 0xb1, 0xed, 0x8b, 0x3c, 0x4e, 0x3e, 0x95, 0xa3, +0x8a, 0x45, 0xd3, 0x34, 0x6e, 0xdb, 0xe1, 0xf9, 0x1b, 0x92, 0xe2, 0x46, 0xb0, 0xb1, 0x6b, 0x89, +0xc5, 0xd3, 0x20, 0xe2, 0x71, 0x74, 0xc8, 0x37, 0x6d, 0xf0, 0xbf, 0x71, 0xb9, 0x2e, 0x24, 0x4b, +0x1e, 0xd8, 0xb5, 0xc4, 0xe2, 0xe9, 0x90, 0x71, 0x38, 0xba, 0x64, 0x1b, 0xb6, 0xf8, 0x5f, 0xb8, +0xdc, 0x97, 0x12, 0x25, 0x8f, 0x6c, 0x5a, 0xe2, 0x71, 0x74, 0xc8, 0x38, 0xa4, 0x7d, 0x2b, 0x46, +0xed, 0xbe, 0x17, 0xee, 0x37, 0x25, 0xc4, 0xd7, 0xc2, 0xbe, 0xd7, 0xfd, 0x87, 0x54, 0x49, 0xa1, +0xaa, 0x6d, 0x43, 0xdc, 0x89, 0x12, 0x36, 0xcd, 0x45, 0x2b, 0x1c, 0xfe, 0xa2, 0x4e, 0x56, 0xf7, +0x5a, 0x3d, 0x0c, 0xf6, 0x49, 0xb9, 0x00, 0x01, 0x41, 0x58, 0x00, 0x00, 0x08, 0x38, 0xc3, 0x7e, +0xa5, 0xfe, 0x68, 0x5e, 0x34, 0x2b, 0xa0, 0x74, 0xf0, 0x2b, 0x5a, 0x99, 0xdd, 0x15, 0x0b, 0x69, +0x96, 0x16, 0xc5, 0x93, 0x17, 0xa3, 0x47, 0x2b, 0x61, 0x63, 0x7b, 0x86, 0xd6, 0x69, 0x7e, 0xa6, +0x9e, 0xf0, 0xda, 0xbd, 0x3f, 0x9a, 0x1d, 0x4d, 0xea, 0xb9, 0xa2, 0xdc, 0xd1, 0xa1, 0x61, 0x63, +0x7f, 0x87, 0x56, 0x69, 0xfc, 0xd0, 0x70, 0xea, 0xcd, 0x2f, 0xd4, 0x84, 0x6f, 0x55, 0xcd, 0x0c, +0xd1, 0xa1, 0x61, 0x63, 0x7f, 0x87, 0x56, 0x69, 0xfe, 0xa4, 0x1c, 0x3a, 0xb3, 0x4c, 0x6f, 0x55, +0xcd, 0x0c, 0xd1, 0xa1, 0x61, 0x63, 0x7f, 0x87, 0x55, 0xe9, 0xa7, 0xaa, 0x1e, 0x70, 0xda, 0xcd, +0x2f, 0xd4, 0xd1, 0xbd, 0x57, 0x34, 0x33, 0x46, 0x8d, 0x8f, 0xb8, 0xed, 0xda, 0x33, 0xf1, 0xa7, +0xfb, 0x36, 0xf8, 0x7d, 0x66, 0x91, 0xf2, 0xfa, 0x3a, 0xa8, 0xdb, 0xb6, 0xf8, 0xf2, 0x4b, 0x2a, +0xf2, 0x50, 0xed, 0xad, 0xa6, 0x94, 0xd0, 0xcd, 0x1d, 0x69, 0x03, 0x18, 0xfe, 0x47, 0xe6, 0x5f, +0x22, 0xe2, 0x71, 0x3e, 0x57, 0x40, 0xd6, 0x26, 0x77, 0x71, 0xcd, 0xa1, 0xa5, 0x6c, 0x5b, 0x2a, +0x8b, 0xd1, 0xa2, 0x05, 0x85, 0x8d, 0xfe, 0x1b, 0x57, 0xa7, 0xf3, 0x41, 0xc3, 0x6a, 0xf4, 0xd3, +0xd5, 0x0e, 0x9e, 0xf5, 0x5c, 0xd1, 0x76, 0x68, 0xd0, 0xb0, 0xb1, 0xbf, 0xc3, 0x6a, 0xf4, 0xbe, +0x68, 0x38, 0x75, 0x5e, 0x9f, 0xcd, 0x08, 0xde, 0xab, 0x9a, 0x19, 0xa3, 0x42, 0xc2, 0xc6, 0xff, +0x00, 0x0d, 0xab, 0xd3, 0x4f, 0x54, 0x1c, 0x3a, 0xb3, 0x4f, 0xf5, 0x20, 0xde, 0xab, 0x9a, 0x23, +0x34, 0x68, 0x58, 0x58, 0xde, 0xe1, 0xb5, 0x9a, 0x5f, 0xa9, 0xa3, 0x86, 0xd6, 0x69, 0x7e, 0xa6, +0x8d, 0xea, 0xb9, 0xa1, 0x9a, 0x34, 0x6c, 0x2c, 0x6f, 0xf0, 0xea, 0xcd, 0x2f, 0xd4, 0x83, 0x87, +0x55, 0xe9, 0xfc, 0xd0, 0x6f, 0x55, 0xcd, 0x0c, 0xd1, 0xa1, 0x61, 0x63, 0x7f, 0x87, 0x56, 0x69, +0xfe, 0xa4, 0x1c, 0x36, 0xb3, 0xdc, 0x4f, 0x54, 0x1b, 0xd5, 0x73, 0x43, 0x34, 0x6f, 0xe1, 0x0c, +0xb4, 0x72, 0xbf, 0xc5, 0xc5, 0xb3, 0x4a, 0x8e, 0x25, 0x86, 0x9d, 0x8c, 0x5e, 0xf7, 0xb7, 0xcd, +0x4d, 0xd3, 0x97, 0x6c, 0xb3, 0xb2, 0x4c, 0xaa, 0x4f, 0x56, 0xc0, 0x00, 0xac, 0x80, 0x00, 0x00, +0x1e, 0x5a, 0xe8, 0xa8, 0xa7, 0xa0, 0x03, 0x95, 0x9a, 0x7a, 0xb8, 0x64, 0x7c, 0x7d, 0xb3, 0xb2, +0x53, 0x16, 0xf7, 0x53, 0xac, 0xff, 0x00, 0x52, 0x9e, 0x29, 0x4f, 0xdc, 0x99, 0xbe, 0x4e, 0x23, +0x1d, 0x4a, 0x76, 0xec, 0x82, 0x78, 0xa1, 0xa9, 0x9b, 0x7b, 0xa9, 0xd6, 0x79, 0xee, 0xf7, 0x53, +0xac, 0xec, 0x94, 0xc1, 0x60, 0x59, 0x84, 0x38, 0xa1, 0x91, 0x9b, 0x7b, 0xa9, 0xd6, 0x78, 0xde, +0xea, 0x75, 0x9f, 0x99, 0x84, 0x0c, 0x21, 0xc5, 0x7b, 0x0c, 0x8c, 0xfb, 0xdd, 0x4e, 0xb3, 0x8f, +0x37, 0xba, 0x9d, 0x57, 0x18, 0x40, 0xc2, 0x1c, 0x50, 0xc8, 0xcf, 0xbd, 0xd4, 0xeb, 0x3c, 0xf8, +0x75, 0x44, 0xcf, 0x6a, 0xb5, 0xd2, 0xba, 0xc6, 0x31, 0x61, 0x84, 0x38, 0xa1, 0x91, 0xd7, 0xc2, +0xed, 0xb8, 0xa3, 0x77, 0x8b, 0x50, 0x9d, 0x8a, 0x5d, 0xb1, 0x44, 0xf6, 0xae, 0x69, 0x27, 0xfb, +0x45, 0x36, 0x28, 0x1d, 0xb7, 0x4b, 0x17, 0x92, 0xa7, 0xa2, 0x9f, 0x18, 0x9b, 0x51, 0xd4, 0xae, +0xf8, 0x2a, 0x29, 0xcc, 0x82, 0xc6, 0xf4, 0xbf, 0xb8, 0x6a, 0x42, 0x5a, 0xba, 0x9d, 0x67, 0x85, +0xaa, 0xa9, 0xd6, 0x79, 0x86, 0xc0, 0xe9, 0xe1, 0x0e, 0x2b, 0xd8, 0x64, 0x67, 0xde, 0xea, 0x75, +0xdf, 0xea, 0x79, 0xbd, 0xd4, 0xeb, 0x3c, 0xc2, 0x06, 0xdc, 0x38, 0xa1, 0x91, 0x9f, 0x7b, 0xa9, +0xd6, 0x71, 0xe2, 0x55, 0xd4, 0xeb, 0x38, 0xc2, 0x06, 0x10, 0xe2, 0xbd, 0x86, 0x46, 0x7d, 0xee, +0xa7, 0x59, 0xd9, 0x20, 0xde, 0xea, 0x75, 0x9c, 0x60, 0xb0, 0x18, 0x43, 0x8a, 0x19, 0x19, 0xb7, +0xba, 0x9d, 0x67, 0x8d, 0xee, 0xa7, 0x59, 0xe6, 0x10, 0x30, 0x87, 0x14, 0x32, 0x33, 0x6f, 0x75, +0x3a, 0xcf, 0x33, 0xd3, 0x4b, 0x53, 0x3c, 0xec, 0x67, 0x6c, 0xff, 0x00, 0x89, 0xa4, 0x5c, 0xc2, +0xe0, 0xd9, 0x6a, 0xca, 0xbc, 0xdd, 0xc8, 0xaa, 0xfc, 0x21, 0x5b, 0x78, 0xad, 0x46, 0xa5, 0x90, +0x01, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xa4, 0x8d, 0xb2, 0xb1, 0xcc, 0x77, +0x25, 0x4c, 0xc8, 0xb2, 0xd2, 0xd1, 0x44, 0xed, 0x97, 0xcc, 0xf6, 0xa9, 0x7c, 0x9d, 0x5d, 0x4b, +0xdb, 0xb2, 0xed, 0xef, 0xb4, 0xb6, 0xa9, 0xe9, 0x2d, 0x32, 0x69, 0x32, 0x24, 0x89, 0xfd, 0x86, +0x1f, 0xd4, 0x38, 0x76, 0x18, 0x76, 0xbb, 0x89, 0xaa, 0x96, 0xc8, 0x58, 0xdf, 0xb7, 0x2f, 0x2c, +0x8a, 0x72, 0xfd, 0x22, 0x97, 0x61, 0x87, 0x75, 0x0a, 0x3b, 0x0c, 0x3f, 0xa8, 0x71, 0x30, 0x0d, +0xa9, 0x79, 0x24, 0x32, 0xfd, 0x22, 0x9f, 0x61, 0x87, 0x6b, 0xb8, 0xf3, 0xb0, 0xc3, 0xb5, 0xdc, +0x4d, 0x03, 0x6d, 0xf9, 0x24, 0x32, 0xfd, 0x22, 0x97, 0x61, 0x87, 0x6b, 0xb8, 0x76, 0x18, 0x77, +0x50, 0xe2, 0x68, 0x1b, 0x6f, 0xc9, 0x21, 0x97, 0xe9, 0x1d, 0x06, 0x1f, 0xd9, 0xa2, 0x4d, 0x1b, +0x1d, 0x74, 0x6b, 0xef, 0xea, 0x86, 0xf4, 0x91, 0xa3, 0xe3, 0x73, 0x55, 0x7b, 0xcd, 0x54, 0xf5, +0x21, 0xe1, 0x6f, 0xd9, 0x9d, 0xcc, 0xf7, 0x99, 0xf3, 0x43, 0xa2, 0x30, 0x5d, 0x17, 0x1b, 0x5f, +0xb9, 0x6c, 0x7b, 0xc4, 0xe6, 0xa3, 0x82, 0x8e, 0xdf, 0xc6, 0x95, 0xcd, 0x7a, 0x2a, 0xa2, 0xa7, +0xb1, 0x16, 0xe6, 0x4e, 0xc3, 0x0e, 0xd7, 0x71, 0x8f, 0x11, 0x8b, 0xb3, 0x9d, 0x5d, 0xef, 0xa6, +0x46, 0x81, 0xb2, 0x11, 0x73, 0x8a, 0x92, 0xb2, 0x5d, 0xca, 0xdb, 0xd3, 0xb6, 0x85, 0x3e, 0xc3, +0x0e, 0xd7, 0x70, 0xec, 0x70, 0xdd, 0x75, 0x26, 0x03, 0xde, 0xdb, 0xf2, 0xc8, 0x8c, 0xbf, 0x48, +0xa7, 0xd8, 0x61, 0xfd, 0x43, 0x87, 0x61, 0x87, 0x75, 0x0a, 0x4d, 0xb0, 0xb0, 0xdb, 0x97, 0x96, +0x43, 0x2f, 0xd2, 0x29, 0x76, 0x18, 0x76, 0xbb, 0x87, 0x61, 0x87, 0xf5, 0x0e, 0x26, 0xd8, 0x58, +0x6d, 0xcb, 0xc9, 0x21, 0x97, 0xe9, 0x14, 0xbb, 0x1c, 0x37, 0x5d, 0x46, 0xef, 0x87, 0x75, 0x0a, +0x4d, 0x16, 0x55, 0xb2, 0x5b, 0x9e, 0x48, 0x46, 0xdb, 0xf2, 0xc8, 0x65, 0xfa, 0x45, 0x68, 0xa9, +0x28, 0x64, 0x76, 0xcb, 0x24, 0x7a, 0xaa, 0x16, 0x5a, 0xd4, 0x63, 0x51, 0xad, 0x4c, 0x91, 0x32, +0x35, 0x28, 0xe9, 0x92, 0x9e, 0x3c, 0xfb, 0xee, 0xcd, 0x54, 0xdf, 0x30, 0x5b, 0x27, 0x29, 0x69, +0x93, 0x69, 0x17, 0x25, 0xd8, 0x00, 0x0a, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x22, 0x57, 0x51, 0xf3, 0x96, 0x34, 0xf1, 0x57, 0x34, 0x8e, 0x76, 0x46, 0xb4, 0xee, 0x91, +0x8d, 0xbc, 0x6c, 0x6b, 0x95, 0x39, 0xa7, 0xb6, 0xc6, 0x9a, 0xba, 0x89, 0x45, 0x28, 0xb5, 0xa9, +0x54, 0xab, 0xd7, 0xba, 0x7a, 0x1c, 0xb0, 0x2b, 0x71, 0x37, 0x26, 0x5d, 0x80, 0xe2, 0x8b, 0xa4, +0x69, 0xce, 0xdf, 0x1a, 0xf7, 0x29, 0xd2, 0x1c, 0xfe, 0x09, 0x20, 0xad, 0xc4, 0xdd, 0xa2, 0x9e, +0xa7, 0x89, 0x89, 0xbb, 0x49, 0x06, 0x76, 0xf8, 0xfe, 0x47, 0xd1, 0xe4, 0xf8, 0x25, 0x02, 0xaf, +0x13, 0x7e, 0x93, 0x47, 0x13, 0x76, 0x8b, 0x06, 0x76, 0xf8, 0xfe, 0x47, 0xd1, 0xcf, 0xe0, 0xd3, +0xa6, 0x7f, 0x67, 0x53, 0x13, 0xbf, 0xaa, 0xca, 0xbf, 0x05, 0xc8, 0xea, 0x8e, 0x76, 0x6a, 0xf5, +0x92, 0x37, 0x33, 0xb2, 0x69, 0x6a, 0x09, 0x3b, 0x48, 0x98, 0xfc, 0xb3, 0x6a, 0x7a, 0xfb, 0x4c, +0xdd, 0x46, 0x4f, 0x19, 0x38, 0xe8, 0x5d, 0x53, 0x5d, 0xd2, 0x7a, 0x98, 0x6b, 0x60, 0x59, 0xe0, +0x54, 0x6f, 0x79, 0x33, 0x43, 0x9a, 0x3b, 0x22, 0x4c, 0xef, 0x75, 0x23, 0xd6, 0x46, 0x35, 0x15, +0x92, 0x2a, 0x5d, 0x3c, 0x1c, 0x28, 0xb5, 0xc7, 0xe9, 0xd3, 0x5f, 0xc0, 0xb2, 0x3a, 0xf7, 0xd4, +0x86, 0x0a, 0xbc, 0x4d, 0xfa, 0x4d, 0x1c, 0x51, 0xda, 0x2d, 0x34, 0xe7, 0x6f, 0x8f, 0xe4, 0xa3, +0xe8, 0xe7, 0xf0, 0x4a, 0x05, 0x5e, 0x26, 0xfd, 0x26, 0x8e, 0x26, 0xfd, 0x26, 0x8c, 0xed, 0xf1, +0xaf, 0x71, 0xf4, 0x79, 0x3e, 0x09, 0x40, 0xab, 0xc5, 0x1d, 0xa2, 0xd0, 0x98, 0x9c, 0x9a, 0x4d, +0x19, 0xdb, 0xe3, 0x5e, 0xe4, 0xfd, 0x1c, 0xfe, 0x09, 0x68, 0x8a, 0xb9, 0x16, 0xe8, 0x28, 0xd5, +0x88, 0x92, 0xbf, 0xbc, 0xa9, 0x92, 0x1b, 0xd0, 0xac, 0x8a, 0xd4, 0x59, 0x1a, 0x8d, 0x76, 0x77, +0x4e, 0x7e, 0xdc, 0xb3, 0x36, 0x4c, 0xb6, 0xf5, 0x12, 0x9a, 0x71, 0x4b, 0x42, 0xe8, 0x56, 0x97, +0x7d, 0x75, 0x00, 0x03, 0x39, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1a, 0x35, 0x14, 0x51, 0xcf, 0x75, 0xe4, 0xef, 0x12, 0x1c, 0xb4, 0xd2, 0xc2, +0xe5, 0xda, 0x6f, 0x92, 0xe6, 0xa8, 0xa7, 0x54, 0x62, 0x7b, 0x1a, 0xf6, 0xaa, 0x39, 0x11, 0x7e, +0x0b, 0x99, 0x75, 0x77, 0xca, 0x1f, 0xb4, 0x55, 0x3a, 0xa3, 0x2e, 0xff, 0x00, 0x73, 0x94, 0x44, +0x08, 0x89, 0xfe, 0xbe, 0x25, 0xa9, 0x70, 0xf6, 0x3a, 0xeb, 0x12, 0xd9, 0xd7, 0xbd, 0x89, 0xd2, +0xd3, 0x4d, 0x1e, 0x4a, 0xcf, 0x67, 0x34, 0xcd, 0x2d, 0xcd, 0x4d, 0x71, 0xba, 0x12, 0xfb, 0x94, +0x4a, 0xb9, 0x47, 0xec, 0x6b, 0x59, 0x33, 0xff, 0x00, 0x62, 0xc9, 0xe0, 0x7b, 0xf1, 0xf8, 0x7f, +0xe8, 0x27, 0x97, 0xff, 0x00, 0x2f, 0xb0, 0xb4, 0xac, 0xf8, 0x2e, 0xe1, 0xb2, 0x6d, 0x42, 0xac, +0xf7, 0x5d, 0xf2, 0x52, 0x1a, 0xfb, 0x33, 0x37, 0xf0, 0xd9, 0x2d, 0x3a, 0xb3, 0xdf, 0x6f, 0xcd, +0x0a, 0xaf, 0x8e, 0x55, 0x33, 0xdd, 0x2f, 0x49, 0x9d, 0x11, 0x89, 0xec, 0x6c, 0x8c, 0x56, 0x3d, +0x2e, 0x8a, 0x99, 0x99, 0x41, 0xce, 0x36, 0x9c, 0xad, 0x45, 0x3a, 0xd3, 0xc8, 0xad, 0x54, 0xfa, +0x2b, 0x75, 0x6a, 0x98, 0x55, 0x2d, 0xec, 0xf8, 0x7e, 0x67, 0x4f, 0x34, 0x2c, 0x95, 0x8a, 0xc7, +0xa5, 0xd1, 0x7d, 0x6e, 0x73, 0xf3, 0x42, 0xe8, 0x1e, 0x8d, 0x7a, 0x65, 0xef, 0x7b, 0x15, 0x0d, +0xf4, 0xdd, 0x9a, 0xd1, 0xfa, 0x99, 0x2c, 0xab, 0x1e, 0xeb, 0xd0, 0xc0, 0xa8, 0x99, 0xf9, 0xa8, +0x44, 0x4c, 0xbc, 0xd0, 0xd8, 0x8e, 0x96, 0x79, 0x39, 0x35, 0x7b, 0xca, 0x8a, 0xab, 0x97, 0xc1, +0x54, 0xa5, 0x0e, 0x1c, 0xc6, 0xfd, 0x62, 0xed, 0x64, 0x89, 0x6c, 0xd1, 0x0f, 0x53, 0xba, 0x10, +0xf5, 0x7e, 0xc4, 0x46, 0xb9, 0x4b, 0xec, 0x4d, 0x82, 0x9e, 0x49, 0xed, 0xb2, 0x99, 0x7b, 0x54, +0xb7, 0x4f, 0x48, 0xca, 0x74, 0x55, 0x4e, 0xf2, 0xa6, 0x6e, 0x36, 0xd1, 0x11, 0x12, 0xc8, 0x87, +0xd1, 0x92, 0xcb, 0xa5, 0x3e, 0xde, 0x88, 0xd1, 0x0a, 0xe3, 0x1f, 0xf2, 0x00, 0x05, 0x25, 0x80, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x07, 0x82, 0xc7, 0xa0, 0x03, 0x4d, 0xd4, 0xd0, 0xbe, 0xfb, 0x51, 0xb6, 0xfc, 0xd5, 0x6d, +0x65, 0xbf, 0x99, 0xaa, 0xec, 0x36, 0x2f, 0xba, 0xe7, 0x21, 0x58, 0x1e, 0xe3, 0x64, 0xe3, 0xe9, +0x26, 0x79, 0x71, 0x8b, 0xfb, 0x22, 0x13, 0xb0, 0xd9, 0x3e, 0xec, 0x8d, 0x5b, 0x27, 0xc5, 0x3f, +0x73, 0xe5, 0x94, 0x55, 0x31, 0xc8, 0xd7, 0xb7, 0x65, 0x6c, 0xa9, 0xf7, 0x8b, 0xe0, 0xf7, 0xbf, +0x66, 0x9a, 0x1e, 0x36, 0xa1, 0xae, 0xa0, 0x00, 0x52, 0x5a, 0x0f, 0x85, 0x6b, 0x55, 0x2c, 0xa8, +0x9c, 0xfc, 0xcf, 0xb0, 0x00, 0xb5, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xd9 + }}; } diff --git a/src/core/constants.h b/src/core/constants.h index f1f67d3b8f..7e0f26441e 100644 --- a/src/core/constants.h +++ b/src/core/constants.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,7 +14,7 @@ // directly including some service header for the sole purpose of data. namespace Core::Constants { -// ACC Service - Blank JPEG used as user icon in absentia of real one. -extern const std::array ACCOUNT_BACKUP_JPEG; +// ACC Service - An Eden Profile Picture JPEG used as user icon in absentia of real one. +extern const std::array ACCOUNT_BACKUP_JPEG; } // namespace Core::Constants diff --git a/src/core/core.cpp b/src/core/core.cpp index bf97184f8f..bada8ef2c1 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -15,6 +15,8 @@ #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" + +#include "launch_timestamp_cache.h" #include "core/core_timing.h" #include "core/cpu_manager.h" #include "core/debugger/debugger.h" @@ -112,11 +114,10 @@ struct System::Impl { u64 program_id; void Initialize(System& system) { - device_memory = std::make_unique(); + device_memory.emplace(); is_multicore = Settings::values.use_multi_core.GetValue(); - extended_memory_layout = - Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; + extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; core_timing.SetMulticore(is_multicore); core_timing.Initialize([&system]() { system.RegisterHostThread(); }); @@ -132,7 +133,7 @@ struct System::Impl { // Create default implementations of applets if one is not provided. frontend_applets.SetDefaultAppletsIfMissing(); - is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); + auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); kernel.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore); @@ -254,7 +255,7 @@ struct System::Impl { } void InitializeDebugger(System& system, u16 port) { - debugger = std::make_unique(system, port); + debugger.emplace(system, port); } void InitializeKernel(System& system) { @@ -268,24 +269,22 @@ struct System::Impl { } SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) { - host1x_core = std::make_unique(system); + host1x_core.emplace(system); gpu_core = VideoCore::CreateGPU(emu_window, system); - if (!gpu_core) { + if (!gpu_core) return SystemResultStatus::ErrorVideoCore; - } - audio_core = std::make_unique(system); + audio_core.emplace(system); service_manager = std::make_shared(kernel); - services = - std::make_unique(service_manager, system, stop_event.get_token()); + services.emplace(service_manager, system, stop_event.get_token()); is_powered_on = true; exit_locked = false; exit_requested = false; if (Settings::values.enable_renderdoc_hotkey) { - renderdoc_api = std::make_unique(); + renderdoc_api.emplace(); } LOG_DEBUG(Core, "Initialized OK"); @@ -303,16 +302,11 @@ struct System::Impl { // Create the application process Loader::ResultStatus load_result{}; std::vector control; - auto process = - Service::AM::CreateApplicationProcess(control, app_loader, load_result, system, file, - params.program_id, params.program_index); - + auto process = Service::AM::CreateApplicationProcess(control, app_loader, load_result, system, file, params.program_id, params.program_index); if (load_result != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result); ShutdownMainProcess(); - - return static_cast( - static_cast(SystemResultStatus::ErrorLoader) + static_cast(load_result)); + return SystemResultStatus(u32(SystemResultStatus::ErrorLoader) + u32(load_result)); } if (!app_loader) { @@ -331,14 +325,16 @@ struct System::Impl { LOG_INFO(Core, "Loading {} ({:016X}) ...", name, params.program_id); + // Track launch time for frontend launches + LaunchTimestampCache::SaveLaunchTimestamp(params.program_id); + // Make the process created be the application kernel.MakeApplicationProcess(process->GetHandle()); // Set up the rest of the system. SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)}; if (init_result != SystemResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", - static_cast(init_result)); + LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", int(init_result)); ShutdownMainProcess(); return init_result; } @@ -361,24 +357,19 @@ struct System::Impl { } } - perf_stats = std::make_unique(params.program_id); + perf_stats.emplace(params.program_id); // Reset counters and set time origin to current frame GetAndResetPerfStats(); perf_stats->BeginSystemFrame(); - std::string title_version; - const FileSys::PatchManager pm(params.program_id, system.GetFileSystemController(), - system.GetContentProvider()); - const auto metadata = pm.GetControlMetadata(); - if (metadata.first != nullptr) { - title_version = metadata.first->GetVersionString(); - } + const FileSys::PatchManager pm(params.program_id, system.GetFileSystemController(), system.GetContentProvider()); + auto const metadata = pm.GetControlMetadata(); + std::string title_version = metadata.first != nullptr ? metadata.first->GetVersionString() : ""; if (app_loader->ReadProgramId(program_id) != Loader::ResultStatus::Success) { LOG_ERROR(Core, "Failed to find program id for ROM"); } - GameSettings::LoadOverrides(program_id, gpu_core->Renderer()); if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; @@ -387,9 +378,7 @@ struct System::Impl { game_info.version = title_version; room_member->SendGameInfo(game_info); } - - status = SystemResultStatus::Success; - return status; + return SystemResultStatus::Success; } void ShutdownMainProcess() { @@ -448,112 +437,79 @@ struct System::Impl { } Loader::ResultStatus GetGameName(std::string& out) const { - if (app_loader == nullptr) - return Loader::ResultStatus::ErrorNotInitialized; - return app_loader->ReadTitle(out); - } - - void SetStatus(SystemResultStatus new_status, const char* details = nullptr) { - status = new_status; - if (details) { - status_details = details; - } + return app_loader ? app_loader->ReadTitle(out) : Loader::ResultStatus::ErrorNotInitialized; } PerfStatsResults GetAndResetPerfStats() { return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); } - mutable std::mutex suspend_guard; - std::atomic_bool is_paused{}; - std::atomic is_shutting_down{}; - Timing::CoreTiming core_timing; Kernel::KernelCore kernel; /// RealVfsFilesystem instance FileSys::VirtualFilesystem virtual_filesystem; - /// ContentProviderUnion instance - std::unique_ptr content_provider; Service::FileSystem::FileSystemController fs_controller; - /// AppLoader used to load the current executing application - std::unique_ptr app_loader; - std::unique_ptr gpu_core; - std::unique_ptr host1x_core; - std::unique_ptr device_memory; - std::unique_ptr audio_core; Core::HID::HIDCore hid_core; - CpuManager cpu_manager; - std::atomic_bool is_powered_on{}; - bool exit_locked = false; - bool exit_requested = false; - - bool nvdec_active{}; - Reporter reporter; - std::unique_ptr cheat_engine; - std::unique_ptr memory_freezer; - std::array build_id{}; - - std::unique_ptr renderdoc_api; - /// Applets Service::AM::AppletManager applet_manager; Service::AM::Frontend::FrontendAppletHolder frontend_applets; - /// APM (Performance) services Service::APM::Controller apm_controller{core_timing}; - /// Service State Service::Glue::ARPManager arp_manager; Service::Account::ProfileManager profile_manager; + /// Network instance + Network::NetworkInstance network_instance; + Core::SpeedLimiter speed_limiter; + ExecuteProgramCallback execute_program_callback; + ExitCallback exit_callback; + + std::optional services; + std::optional debugger; + std::optional general_channel_context; + std::optional general_channel_event; + std::optional perf_stats; + std::optional host1x_core; + std::optional device_memory; + std::optional audio_core; + std::optional cheat_engine; + std::optional memory_freezer; + std::optional renderdoc_api; + + std::array gpu_dirty_memory_managers; + std::vector> user_channel; + std::vector> general_channel; + + std::array dynarmic_ticks{}; + std::array build_id{}; /// Service manager std::shared_ptr service_manager; - - /// Services - std::unique_ptr services; - - /// Network instance - Network::NetworkInstance network_instance; - - /// Debugger - std::unique_ptr debugger; - - SystemResultStatus status = SystemResultStatus::Success; - std::string status_details = ""; - - std::unique_ptr perf_stats; - Core::SpeedLimiter speed_limiter; - - bool is_multicore{}; - bool is_async_gpu{}; - bool extended_memory_layout{}; - - ExecuteProgramCallback execute_program_callback; - ExitCallback exit_callback; + /// ContentProviderUnion instance + std::unique_ptr content_provider; + /// AppLoader used to load the current executing application + std::unique_ptr app_loader; + std::unique_ptr gpu_core; std::stop_source stop_event; - std::array dynarmic_ticks{}; - - std::array - gpu_dirty_memory_managers; - - std::deque> user_channel; - + mutable std::mutex suspend_guard; std::mutex general_channel_mutex; - std::deque> general_channel; - std::unique_ptr general_channel_context; // lazy - std::unique_ptr general_channel_event; // lazy - bool general_channel_initialized{false}; + std::atomic_bool is_paused{}; + std::atomic_bool is_shutting_down{}; + std::atomic_bool is_powered_on{}; + bool is_multicore : 1 = false; + bool extended_memory_layout : 1 = false; + bool exit_locked : 1 = false; + bool exit_requested : 1 = false; + bool nvdec_active : 1 = false; void EnsureGeneralChannelInitialized(System& system) { - if (general_channel_initialized) { - return; + if (!general_channel_event) { + general_channel_context.emplace(system, "GeneralChannel"); + general_channel_event.emplace(*general_channel_context); } - general_channel_context = std::make_unique(system, "GeneralChannel"); - general_channel_event = std::make_unique(*general_channel_context); - general_channel_initialized = true; } }; @@ -776,14 +732,6 @@ Loader::ResultStatus System::GetGameName(std::string& out) const { return impl->GetGameName(out); } -void System::SetStatus(SystemResultStatus new_status, const char* details) { - impl->SetStatus(new_status, details); -} - -const std::string& System::GetStatusDetails() const { - return impl->status_details; -} - Loader::AppLoader& System::GetAppLoader() { return *impl->app_loader; } @@ -803,7 +751,7 @@ FileSys::VirtualFilesystem System::GetFilesystem() const { void System::RegisterCheatList(const std::vector& list, const std::array& build_id, u64 main_region_begin, u64 main_region_size) { - impl->cheat_engine = std::make_unique(*this, list, build_id); + impl->cheat_engine.emplace(*this, list, build_id); impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size); } @@ -964,11 +912,13 @@ void System::ExecuteProgram(std::size_t program_index) { } } -std::deque>& System::GetUserChannel() { +/// @brief Gets a reference to the user channel stack. +/// It is used to transfer data between programs. +std::vector>& System::GetUserChannel() { return impl->user_channel; } -std::deque>& System::GetGeneralChannel() { +std::vector>& System::GetGeneralChannel() { return impl->general_channel; } @@ -984,7 +934,7 @@ void System::PushGeneralChannelData(std::vector&& data) { bool System::TryPopGeneralChannel(std::vector& out_data) { std::scoped_lock lk{impl->general_channel_mutex}; - if (!impl->general_channel_initialized || impl->general_channel.empty()) { + if (!impl->general_channel_event || impl->general_channel.empty()) { return false; } out_data = std::move(impl->general_channel.back()); diff --git a/src/core/core.h b/src/core/core.h index 60bf73d4e1..702c5cc81b 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -325,10 +325,6 @@ public: /// Gets the name of the current game [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; - void SetStatus(SystemResultStatus new_status, const char* details); - - [[nodiscard]] const std::string& GetStatusDetails() const; - [[nodiscard]] Loader::AppLoader& GetAppLoader(); [[nodiscard]] const Loader::AppLoader& GetAppLoader() const; @@ -424,13 +420,8 @@ public: */ void ExecuteProgram(std::size_t program_index); - /** - * Gets a reference to the user channel stack. - * It is used to transfer data between programs. - */ - [[nodiscard]] std::deque>& GetUserChannel(); - - [[nodiscard]] std::deque>& GetGeneralChannel(); + [[nodiscard]] std::vector>& GetUserChannel(); + [[nodiscard]] std::vector>& GetGeneralChannel(); void PushGeneralChannelData(std::vector&& data); bool TryPopGeneralChannel(std::vector& out_data); [[nodiscard]] Service::Event& GetGeneralChannelEvent(); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 3c847c8359..5a582c8cff 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -53,13 +53,6 @@ CoreTiming::~CoreTiming() { Reset(); } -void CoreTiming::ThreadEntry(CoreTiming& instance) { - Common::SetCurrentThreadName("HostTiming"); - Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - instance.on_thread_init(); - instance.ThreadLoop(); -} - void CoreTiming::Initialize(std::function&& on_thread_init_) { Reset(); on_thread_init = std::move(on_thread_init_); @@ -67,7 +60,12 @@ void CoreTiming::Initialize(std::function&& on_thread_init_) { shutting_down = false; cpu_ticks = 0; if (is_multicore) { - timer_thread = std::make_unique(ThreadEntry, std::ref(*this)); + timer_thread.emplace([](CoreTiming& instance) { + Common::SetCurrentThreadName("HostTiming"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + instance.on_thread_init(); + instance.ThreadLoop(); + }, std::ref(*this)); } } diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 7e4dff7f3d..ae9f56d519 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -140,8 +143,6 @@ public: private: struct Event; - - static void ThreadEntry(CoreTiming& instance); void ThreadLoop(); void Reset(); @@ -164,7 +165,7 @@ private: Common::Event pause_event{}; mutable std::mutex basic_lock; std::mutex advance_lock; - std::unique_ptr timer_thread; + std::optional timer_thread; std::atomic paused{}; std::atomic paused_set{}; std::atomic wait_set{}; diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 6dcf7bb228..41227591c7 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,6 +12,8 @@ #include #include #include +#include +#include #include "common/common_types.h" #include "common/range_mutex.h" @@ -44,6 +49,7 @@ public: ~DeviceMemoryManager(); static constexpr bool HAS_FLUSH_INVALIDATION = true; + static constexpr size_t AS_BITS = Traits::device_virtual_bits; void BindInterface(DeviceInterface* device_inter); @@ -117,7 +123,12 @@ public: void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta); - static constexpr size_t AS_BITS = Traits::device_virtual_bits; + // New batch API to update multiple ranges with a single lock acquisition. + void UpdatePagesCachedBatch(std::span> ranges, s32 delta); + +private: + // Internal helper that performs the update assuming the caller already holds the necessary lock. + void UpdatePagesCachedCountNoLock(DAddr addr, size_t size, s32 delta); private: static constexpr size_t device_virtual_bits = Traits::device_virtual_bits; @@ -214,6 +225,8 @@ private: std::unique_ptr cached_pages; Common::RangeMutex counter_guard; std::mutex mapping_guard; + + }; } // namespace Core diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 52dff5df9a..4be26d9631 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,6 +8,8 @@ #include #include #include +#include +#include #include "common/address_space.h" #include "common/address_space.inc" @@ -507,8 +512,7 @@ void DeviceMemoryManager::UnregisterProcess(Asid asid) { } template -void DeviceMemoryManager::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) { - Common::ScopedRangeLock lk(counter_guard, addr, size); +void DeviceMemoryManager::UpdatePagesCachedCountNoLock(DAddr addr, size_t size, s32 delta) { u64 uncache_begin = 0; u64 cache_begin = 0; u64 uncache_bytes = 0; @@ -586,4 +590,47 @@ void DeviceMemoryManager::UpdatePagesCachedCount(DAddr addr, size_t size release_pending(); } +template +void DeviceMemoryManager::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) { + Common::ScopedRangeLock lk(counter_guard, addr, size); + UpdatePagesCachedCountNoLock(addr, size, delta); +} + +template +void DeviceMemoryManager::UpdatePagesCachedBatch(std::span> ranges, s32 delta) { + if (ranges.empty()) { + return; + } + // Make a local copy and sort by address + std::vector> tmp(ranges.begin(), ranges.end()); + std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Coalesce adjacent/overlapping ranges + std::vector> coalesced; + DAddr cur_addr = tmp[0].first; + size_t cur_size = tmp[0].second; + for (size_t i = 1; i < tmp.size(); ++i) { + DAddr next_addr = tmp[i].first; + size_t next_size = tmp[i].second; + if (cur_addr + cur_size >= next_addr) { + // overlapping or contiguous + const DAddr end = std::max(cur_addr + cur_size, next_addr + next_size); + cur_size = end - cur_addr; + } else { + coalesced.emplace_back(cur_addr, cur_size); + cur_addr = next_addr; + cur_size = next_size; + } + } + coalesced.emplace_back(cur_addr, cur_size); + + const DAddr lock_begin = coalesced.front().first; + const DAddr lock_end = coalesced.back().first + coalesced.back().second; + Common::ScopedRangeLock lk(counter_guard, lock_begin, static_cast(lock_end - lock_begin)); + + for (const auto& [addr, size] : coalesced) { + UpdatePagesCachedCountNoLock(addr, size, delta); + } +} + } // namespace Core diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index f985943358..6f150ff583 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -85,6 +88,14 @@ const LanguageEntry& NACP::GetLanguageEntry() const { return raw.language_entries.at(static_cast(Language::AmericanEnglish)); } +std::array NACP::GetApplicationNames() const { + std::array names{}; + for (size_t i = 0; i < raw.language_entries.size(); ++i) { + names[i] = raw.language_entries[i].GetApplicationName(); + } + return names; +} + std::string NACP::GetApplicationName() const { return GetLanguageEntry().GetApplicationName(); } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 25668e32c1..bd109f783f 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -131,6 +131,7 @@ public: u64 GetDefaultNormalSaveSize() const; u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; + std::array GetApplicationNames() const; std::vector GetRawBytes() const; bool GetUserAccountSwitchLock() const; u64 GetDeviceSaveDataSize() const; diff --git a/src/core/file_sys/fs_filesystem.h b/src/core/file_sys/fs_filesystem.h index 329b5aca57..e895502e9a 100644 --- a/src/core/file_sys/fs_filesystem.h +++ b/src/core/file_sys/fs_filesystem.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -43,17 +46,31 @@ struct FileSystemAttribute { u8 file_entry_name_length_max_defined; u8 dir_path_name_length_max_defined; u8 file_path_name_length_max_defined; - INSERT_PADDING_BYTES_NOINIT(0x5); + + u8 utf16_create_dir_path_len_max_defined; + u8 utf16_delete_dir_path_len_max_defined; + u8 utf16_rename_src_dir_path_len_max_defined; + u8 utf16_rename_dest_dir_path_len_max_defined; + u8 utf16_open_dir_path_len_max_defined; + u8 utf16_dir_entry_name_length_max_defined; u8 utf16_file_entry_name_length_max_defined; u8 utf16_dir_path_name_length_max_defined; u8 utf16_file_path_name_length_max_defined; + INSERT_PADDING_BYTES_NOINIT(0x18); + s32 dir_entry_name_length_max; s32 file_entry_name_length_max; s32 dir_path_name_length_max; s32 file_path_name_length_max; - INSERT_PADDING_WORDS_NOINIT(0x5); + + s32 utf16_create_dir_path_length_max; + s32 utf16_delete_dir_path_length_max; + s32 utf16_rename_src_dir_path_length_max; + s32 utf16_rename_dest_dir_path_length_max; + s32 utf16_open_dir_path_length_max; + s32 utf16_dir_entry_name_length_max; s32 utf16_file_entry_name_length_max; s32 utf16_dir_path_name_length_max; diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index e564ea831e..a28930a59a 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -15,7 +15,7 @@ namespace HLE::ApiVersion { // Horizon OS version constants. constexpr u8 HOS_VERSION_MAJOR = 21; -constexpr u8 HOS_VERSION_MINOR = 1; +constexpr u8 HOS_VERSION_MINOR = 2; constexpr u8 HOS_VERSION_MICRO = 0; // NintendoSDK version constants. @@ -24,9 +24,9 @@ constexpr u8 SDK_REVISION_MAJOR = 1; constexpr u8 SDK_REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "0d7a6334ddc3d637f69ec3976b17a260efd68fd4"; -constexpr char DISPLAY_VERSION[] = "21.1.0"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 21.1.0-1.0"; +constexpr char VERSION_HASH[] = "ff8d6ddacae7c7fd1287e22c3c88bb961acb290c"; +constexpr char DISPLAY_VERSION[] = "21.2.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 21.2.0-1.0"; // Atmosphere version constants. diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 322f971ba3..082049f957 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1148,9 +1148,17 @@ Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {} KProcess::KProcess(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel}, - m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()}, - m_handle_table{kernel}, m_exclusive_monitor{}, m_memory{kernel.System()} {} + : KAutoObjectWithSlabHeapAndContainer(kernel) + , m_exclusive_monitor{} + , m_memory{kernel.System()} + , m_handle_table{kernel} + , m_page_table{kernel} + , m_state_lock{kernel} + , m_list_lock{kernel} + , m_cond_var{kernel.System()} + , m_address_arbiter{kernel.System()} +{} + KProcess::~KProcess() = default; Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 92ddb1aca4..13717cc090 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -66,60 +66,55 @@ public: private: using SharedMemoryInfoList = Common::IntrusiveListBaseTraits::ListType; - using TLPTree = - Common::IntrusiveRedBlackTreeBaseTraits::TreeType; + using TLPTree = Common::IntrusiveRedBlackTreeBaseTraits::TreeType; using TLPIterator = TLPTree::iterator; private: - KProcessPageTable m_page_table; - std::atomic m_used_kernel_memory_size{}; - TLPTree m_fully_used_tlp_tree{}; - TLPTree m_partially_used_tlp_tree{}; - s32 m_ideal_core_id{}; - KResourceLimit* m_resource_limit{}; - KSystemResource* m_system_resource{}; - size_t m_memory_release_hint{}; - State m_state{}; - KLightLock m_state_lock; - KLightLock m_list_lock; - KConditionVariable m_cond_var; - KAddressArbiter m_address_arbiter; - std::array m_entropy{}; - bool m_is_signaled{}; - bool m_is_initialized{}; - u32 m_pointer_buffer_size = 0x8000; // Default pointer buffer size (can be game-specific later) - bool m_is_application{}; - bool m_is_default_application_system_resource{}; - bool m_is_hbl{}; - std::array m_name{}; - std::atomic m_num_running_threads{}; - Svc::CreateProcessFlag m_flags{}; - KMemoryManager::Pool m_memory_pool{}; - s64 m_schedule_count{}; - KCapabilities m_capabilities{}; - u64 m_program_id{}; - u64 m_process_id{}; - KProcessAddress m_code_address{}; - size_t m_code_size{}; - size_t m_main_thread_stack_size{}; - size_t m_max_process_memory{}; - u32 m_version{}; - KHandleTable m_handle_table; - KProcessAddress m_plr_address{}; - KThread* m_exception_thread{}; - ThreadList m_thread_list{}; - SharedMemoryInfoList m_shared_memory_list{}; - bool m_is_suspended{}; - bool m_is_immortal{}; - bool m_is_handle_table_initialized{}; - std::array, Core::Hardware::NUM_CPU_CORES> - m_arm_interfaces{}; + std::array, Core::Hardware::NUM_CPU_CORES> m_arm_interfaces{}; std::array m_running_threads{}; std::array m_running_thread_idle_counts{}; std::array m_running_thread_switch_counts{}; std::array m_pinned_threads{}; std::array m_watchpoints{}; std::map m_debug_page_refcounts{}; +#ifdef HAS_NCE + std::unordered_map m_post_handlers{}; +#endif + std::unique_ptr m_exclusive_monitor; + Core::Memory::Memory m_memory; + KCapabilities m_capabilities{}; + KProcessAddress m_code_address{}; + KHandleTable m_handle_table; + KProcessAddress m_plr_address{}; + ThreadList m_thread_list{}; + SharedMemoryInfoList m_shared_memory_list{}; + KProcessPageTable m_page_table; + std::atomic m_used_kernel_memory_size{}; + TLPTree m_fully_used_tlp_tree{}; + TLPTree m_partially_used_tlp_tree{}; + State m_state{}; + KLightLock m_state_lock; + KLightLock m_list_lock; + KConditionVariable m_cond_var; + KAddressArbiter m_address_arbiter; + std::array m_entropy{}; + u32 m_pointer_buffer_size = 0x8000; // Default pointer buffer size (can be game-specific later) + std::array m_name{}; + Svc::CreateProcessFlag m_flags{}; + KMemoryManager::Pool m_memory_pool{}; + + KResourceLimit* m_resource_limit{}; + KSystemResource* m_system_resource{}; + KThread* m_exception_thread{}; + + size_t m_code_size{}; + size_t m_main_thread_stack_size{}; + size_t m_max_process_memory{}; + size_t m_memory_release_hint{}; + s64 m_schedule_count{}; + u64 m_program_id{}; + u64 m_process_id{}; + std::atomic m_cpu_time{}; std::atomic m_num_process_switches{}; std::atomic m_num_thread_switches{}; @@ -128,11 +123,20 @@ private: std::atomic m_num_ipc_messages{}; std::atomic m_num_ipc_replies{}; std::atomic m_num_ipc_receives{}; -#ifdef HAS_NCE - std::unordered_map m_post_handlers{}; -#endif - std::unique_ptr m_exclusive_monitor; - Core::Memory::Memory m_memory; + + s32 m_ideal_core_id{}; + u32 m_version{}; + + std::atomic m_num_running_threads{}; + + bool m_is_signaled : 1 = false; + bool m_is_initialized : 1 = false; + bool m_is_application : 1 = false; + bool m_is_default_application_system_resource : 1 = false; + bool m_is_hbl : 1 = false; + bool m_is_suspended : 1 = false; + bool m_is_immortal : 1 = false; + bool m_is_handle_table_initialized : 1 = false; private: Result StartTermination(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 062387a29b..6986a98e35 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -88,11 +88,11 @@ struct KernelCore::Impl { } void Initialize(KernelCore& kernel) { - hardware_timer = std::make_unique(kernel); + hardware_timer.emplace(kernel); hardware_timer->Initialize(); - global_object_list_container = std::make_unique(kernel); - global_scheduler_context = std::make_unique(kernel); + global_object_list_container.emplace(kernel); + global_scheduler_context.emplace(kernel); // Derive the initial memory layout from the emulated board Init::InitializeSlabResourceCounts(kernel); @@ -212,10 +212,9 @@ struct KernelCore::Impl { void InitializePhysicalCores() { for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - const s32 core{static_cast(i)}; - - schedulers[i] = std::make_unique(system.Kernel()); - cores[i] = std::make_unique(system.Kernel(), i); + auto const core = s32(i); + schedulers[i].emplace(system.Kernel()); + cores[i].emplace(system.Kernel(), i); auto* main_thread{Kernel::KThread::Create(system.Kernel())}; main_thread->SetCurrentCore(core); @@ -280,57 +279,56 @@ struct KernelCore::Impl { size -= rc_size; // Initialize the resource managers' shared page manager. - resource_manager_page_manager = std::make_unique(); + resource_manager_page_manager.emplace(); resource_manager_page_manager->Initialize(address, size, std::max(PageSize, KPageBufferSlabHeap::BufferSize)); // Initialize the KPageBuffer slab heap. page_buffer_slab_heap.Initialize(system); // Initialize the fixed-size slabheaps. - app_memory_block_heap = std::make_unique(); - sys_memory_block_heap = std::make_unique(); - block_info_heap = std::make_unique(); - app_memory_block_heap->Initialize(resource_manager_page_manager.get(), ApplicationMemoryBlockSlabHeapSize); - sys_memory_block_heap->Initialize(resource_manager_page_manager.get(), SystemMemoryBlockSlabHeapSize); - block_info_heap->Initialize(resource_manager_page_manager.get(), BlockInfoSlabHeapSize); + app_memory_block_heap.emplace(); + sys_memory_block_heap.emplace(); + block_info_heap.emplace(); + app_memory_block_heap->Initialize(std::addressof(*resource_manager_page_manager), ApplicationMemoryBlockSlabHeapSize); + sys_memory_block_heap->Initialize(std::addressof(*resource_manager_page_manager), SystemMemoryBlockSlabHeapSize); + block_info_heap->Initialize(std::addressof(*resource_manager_page_manager), BlockInfoSlabHeapSize); // Reserve all but a fixed number of remaining pages for the page table heap. const size_t num_pt_pages = resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() - ReservedDynamicPageCount; - page_table_heap = std::make_unique(); + page_table_heap.emplace(); // TODO(bunnei): Pass in address once we support kernel virtual memory allocations. page_table_heap->Initialize( - resource_manager_page_manager.get(), num_pt_pages, + std::addressof(*resource_manager_page_manager), num_pt_pages, /*GetPointer(address + size)*/ nullptr); // Setup the slab managers. KDynamicPageManager* const app_dynamic_page_manager = nullptr; KDynamicPageManager* const sys_dynamic_page_manager = /*KTargetSystem::IsDynamicResourceLimitsEnabled()*/ true - ? resource_manager_page_manager.get() - : nullptr; - app_memory_block_manager = std::make_unique(); - sys_memory_block_manager = std::make_unique(); - app_block_info_manager = std::make_unique(); - sys_block_info_manager = std::make_unique(); - app_page_table_manager = std::make_unique(); - sys_page_table_manager = std::make_unique(); + ? std::addressof(*resource_manager_page_manager) : nullptr; + app_memory_block_manager.emplace(); + sys_memory_block_manager.emplace(); + app_block_info_manager.emplace(); + sys_block_info_manager.emplace(); + app_page_table_manager.emplace(); + sys_page_table_manager.emplace(); - app_memory_block_manager->Initialize(app_dynamic_page_manager, app_memory_block_heap.get()); - sys_memory_block_manager->Initialize(sys_dynamic_page_manager, sys_memory_block_heap.get()); + app_memory_block_manager->Initialize(app_dynamic_page_manager, std::addressof(*app_memory_block_heap)); + sys_memory_block_manager->Initialize(sys_dynamic_page_manager, std::addressof(*sys_memory_block_heap)); - app_block_info_manager->Initialize(app_dynamic_page_manager, block_info_heap.get()); - sys_block_info_manager->Initialize(sys_dynamic_page_manager, block_info_heap.get()); + app_block_info_manager->Initialize(app_dynamic_page_manager, std::addressof(*block_info_heap)); + sys_block_info_manager->Initialize(sys_dynamic_page_manager, std::addressof(*block_info_heap)); - app_page_table_manager->Initialize(app_dynamic_page_manager, page_table_heap.get()); - sys_page_table_manager->Initialize(sys_dynamic_page_manager, page_table_heap.get()); + app_page_table_manager->Initialize(app_dynamic_page_manager, std::addressof(*page_table_heap)); + sys_page_table_manager->Initialize(sys_dynamic_page_manager, std::addressof(*page_table_heap)); // Check that we have the correct number of dynamic pages available. ASSERT(resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() == ReservedDynamicPageCount); // Create the system page table managers. - app_system_resource = std::make_unique(kernel); - sys_system_resource = std::make_unique(kernel); + app_system_resource.emplace(kernel); + sys_system_resource.emplace(kernel); KAutoObject::Create(std::addressof(*app_system_resource)); KAutoObject::Create(std::addressof(*sys_system_resource)); @@ -349,7 +347,7 @@ struct KernelCore::Impl { } void InitializeGlobalData(KernelCore& kernel) { - object_name_global_data = std::make_unique(kernel); + object_name_global_data.emplace(kernel); } void MakeApplicationProcess(KProcess* process) { @@ -431,7 +429,7 @@ struct KernelCore::Impl { } void DeriveInitialMemoryLayout() { - memory_layout = std::make_unique(); + memory_layout.emplace(); // Insert the root region for the virtual memory tree, from which all other regions will // derive. @@ -726,7 +724,7 @@ struct KernelCore::Impl { void InitializeMemoryLayout() { // Initialize the memory manager. - memory_manager = std::make_unique(system); + memory_manager.emplace(system); const auto& management_region = memory_layout->GetPoolManagementRegion(); ASSERT(management_region.GetEndAddress() != 0); memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize()); @@ -774,8 +772,8 @@ struct KernelCore::Impl { std::mutex process_list_lock; std::vector process_list; KProcess* application_process{}; - std::unique_ptr global_scheduler_context; - std::unique_ptr hardware_timer; + std::optional global_scheduler_context; + std::optional hardware_timer; Init::KSlabResourceCounts slab_resource_counts{}; KResourceLimit* system_resource_limit{}; @@ -784,9 +782,9 @@ struct KernelCore::Impl { std::shared_ptr preemption_event; - std::unique_ptr global_object_list_container; + std::optional global_object_list_container; - std::unique_ptr object_name_global_data; + std::optional object_name_global_data; std::unordered_set registered_objects; std::unordered_set registered_in_use_objects; @@ -794,28 +792,28 @@ struct KernelCore::Impl { std::mutex server_lock; std::vector> server_managers; - std::array, Core::Hardware::NUM_CPU_CORES> cores; + std::array, Core::Hardware::NUM_CPU_CORES> cores; // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others std::atomic next_host_thread_id{Core::Hardware::NUM_CPU_CORES}; // Kernel memory management - std::unique_ptr memory_manager; + std::optional memory_manager; // Resource managers - std::unique_ptr resource_manager_page_manager; - std::unique_ptr page_table_heap; - std::unique_ptr app_memory_block_heap; - std::unique_ptr sys_memory_block_heap; - std::unique_ptr block_info_heap; - std::unique_ptr app_page_table_manager; - std::unique_ptr sys_page_table_manager; - std::unique_ptr app_memory_block_manager; - std::unique_ptr sys_memory_block_manager; - std::unique_ptr app_block_info_manager; - std::unique_ptr sys_block_info_manager; - std::unique_ptr app_system_resource; - std::unique_ptr sys_system_resource; + std::optional resource_manager_page_manager; + std::optional page_table_heap; + std::optional app_memory_block_heap; + std::optional sys_memory_block_heap; + std::optional block_info_heap; + std::optional app_page_table_manager; + std::optional sys_page_table_manager; + std::optional app_memory_block_manager; + std::optional sys_memory_block_manager; + std::optional app_block_info_manager; + std::optional sys_block_info_manager; + std::optional app_system_resource; + std::optional sys_system_resource; // Shared memory for services Kernel::KSharedMemory* hid_shared_mem{}; @@ -825,10 +823,10 @@ struct KernelCore::Impl { Kernel::KSharedMemory* hidbus_shared_mem{}; // Memory layout - std::unique_ptr memory_layout; + std::optional memory_layout; std::array shutdown_threads{}; - std::array, Core::Hardware::NUM_CPU_CORES> schedulers{}; + std::array, Core::Hardware::NUM_CPU_CORES> schedulers{}; bool is_multicore{}; std::atomic_bool is_shutting_down{}; @@ -948,12 +946,9 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { } Kernel::KScheduler* KernelCore::CurrentScheduler() { - const u32 core_id = impl->GetCurrentHostThreadID(); - if (core_id >= Core::Hardware::NUM_CPU_CORES) { - // This is expected when called from not a guest thread - return {}; - } - return impl->schedulers[core_id].get(); + if (auto const core_id = impl->GetCurrentHostThreadID(); core_id < Core::Hardware::NUM_CPU_CORES) + return std::addressof(*impl->schedulers[core_id]); + return {}; // This is expected when called from not a guest thread } Kernel::KHardwareTimer& KernelCore::HardwareTimer() { diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp index 807e604d2f..586cb2b14e 100644 --- a/src/core/hle/kernel/svc/svc_event.cpp +++ b/src/core/hle/kernel/svc/svc_event.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -9,7 +9,6 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/svc.h" namespace Kernel::Svc { @@ -20,6 +19,20 @@ Result SignalEvent(Core::System& system, Handle event_handle) { // Get the current handle table. const KHandleTable& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); + // Fail-safe for system applets + const auto program_id = GetCurrentProcess(system.Kernel()).GetProgramId(); + if ((program_id & 0xFFFFFFFFFFFFFF00ull) == 0x0100000000001000ull) { + KScopedAutoObject event = handle_table.GetObject(event_handle); + if (event.IsNotNull()) { + event->Signal(); + } else { + LOG_WARNING(Kernel_SVC, "SignalEvent best-effort unknown handle=0x{:08X} (ignored)", + event_handle); + } + R_SUCCEED(); + } + + // Get the event. KScopedAutoObject event = handle_table.GetObject(event_handle); R_UNLESS(event.IsNotNull(), ResultInvalidHandle); diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 3322839e6a..06ebb22be3 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -12,7 +12,7 @@ #include "common/common_types.h" #include "common/expected.h" -// All the constants in this file come from +// All the constants in this file come from /** * Identifies the module which caused the error. Error codes can be propagated through a call @@ -43,7 +43,7 @@ enum class ErrorModule : u32 { NCMContent = 20, SM = 21, RO = 22, - GC = 23, + Gc = 23, SDMMC = 24, OVLN = 25, SPL = 26, @@ -72,7 +72,7 @@ enum class ErrorModule : u32 { Bluetooth = 113, VI = 114, NFP = 115, - Time = 116, + TimeService = 116, FGM = 117, OE = 118, BH1730FVC = 119, diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index d2070b0b74..b4d6e2ac30 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -138,12 +138,13 @@ private: R_SUCCEED(); } - Result GetNetworkServiceLicenseCacheEx() { + Result GetNetworkServiceLicenseCacheEx(Out out_license, Out out_expiration) { LOG_DEBUG(Service_ACC, "(STUBBED) called."); - // TODO (jarrodnorwell) + *out_license = 0; + *out_expiration = 0; - R_RETURN(ResultUnknown); + R_SUCCEED(); } Common::UUID account_id; diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h index 621a7b4921..beb52b74dd 100644 --- a/src/core/hle/service/am/am_types.h +++ b/src/core/hle/service/am/am_types.h @@ -94,6 +94,7 @@ enum class AppletId : u32 { LoginShare = 0x18, WebAuth = 0x19, MyPage = 0x1A, + Lhub = 0x35 }; enum class AppletProgramId : u64 { diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index 0763a5838e..a693a47d7a 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -95,9 +95,9 @@ struct Applet { bool request_exit_to_library_applet_at_execute_next_program_enabled{}; // Channels - std::deque> user_channel_launch_parameter{}; - std::deque> preselected_user_launch_parameter{}; - std::deque> friend_invitation_storage_channel{}; + std::vector> user_channel_launch_parameter{}; + std::vector> preselected_user_launch_parameter{}; + std::vector> friend_invitation_storage_channel{}; // Context Stack std::stack> context_stack{}; diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp index 8246b3e88e..206f2fd495 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp @@ -236,10 +236,6 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr applet_, WebBrowser::~WebBrowser() = default; void WebBrowser::Initialize() { - if (Settings::values.disable_web_applet) { - return; - } - FrontendApplet::Initialize(); LOG_INFO(Service_AM, "Initializing Web Browser Applet."); @@ -264,6 +260,12 @@ void WebBrowser::Initialize() { LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", web_arg_header.total_tlv_entries, web_arg_header.shim_kind); + if (Settings::values.disable_web_applet && + web_arg_header.shim_kind != ShimKind::Web && + web_arg_header.shim_kind != ShimKind::Lhub) { + return; + } + ExtractSharedFonts(system); switch (web_arg_header.shim_kind) { @@ -288,6 +290,9 @@ void WebBrowser::Initialize() { case ShimKind::Lobby: InitializeLobby(); break; + case ShimKind::Lhub: + InitializeLhub(); + break; default: ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); break; @@ -303,8 +308,19 @@ void WebBrowser::ExecuteInteractive() { } void WebBrowser::Execute() { + if (web_arg_header.shim_kind == ShimKind::Web) { + ExecuteWeb(); + return; + } + + if (web_arg_header.shim_kind == ShimKind::Lhub) { + ExecuteLhub(); + return; + } + if (Settings::values.disable_web_applet) { - LOG_WARNING(Service_AM, "(STUBBED) called, Web Browser Applet is disabled"); + LOG_WARNING(Service_AM, "(STUBBED) called, Web Browser Applet is disabled. shim_kind={}", + web_arg_header.shim_kind); WebBrowserExit(WebExitReason::EndButtonPressed); return; } @@ -331,6 +347,9 @@ void WebBrowser::Execute() { case ShimKind::Lobby: ExecuteLobby(); break; + case ShimKind::Lhub: + ExecuteLhub(); + break; default: ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); WebBrowserExit(WebExitReason::EndButtonPressed); @@ -351,17 +370,99 @@ void WebBrowser::ExtractOfflineRomFS() { } void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { - if ((web_arg_header.shim_kind == ShimKind::Share && + const bool use_tlv_output = + (web_arg_header.shim_kind == ShimKind::Share && web_applet_version >= WebAppletVersion::Version196608) || (web_arg_header.shim_kind == ShimKind::Web && - web_applet_version >= WebAppletVersion::Version524288)) { - // TODO: Push Output TLVs instead of a WebCommonReturnValue + web_applet_version >= WebAppletVersion::Version524288) || + (web_arg_header.shim_kind == ShimKind::Lhub); + + // https://switchbrew.org/wiki/Internet_Browser#TLVs + if (use_tlv_output) { + LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", + exit_reason, last_url, last_url.size()); + + // storage size for TLVs is 0x2000 bytes (as per switchbrew documentation) + constexpr size_t TLV_STORAGE_SIZE = 0x2000; + std::vector out_data(TLV_STORAGE_SIZE, 0); + + size_t current_offset = sizeof(WebArgHeader); + u16 tlv_count = 0; + + // align and matchng TLV struct alignment + auto align_offset = [](size_t offset) -> size_t { + return (offset + 7) & ~static_cast(7); + }; + + // 0x1 ShareExitReason + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::ShareExitReason; + tlv.arg_data_size = sizeof(u32); + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + const u32 exit_reason_value = static_cast(exit_reason); + std::memcpy(out_data.data() + current_offset, &exit_reason_value, sizeof(u32)); + current_offset += sizeof(u32); + + current_offset = align_offset(current_offset); + tlv_count++; + } + + // 0x2 LastUrl + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::LastURL; + const u16 url_data_size = static_cast(last_url.size() + 1); + tlv.arg_data_size = url_data_size; + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + // null terminator + std::memcpy(out_data.data() + current_offset, last_url.c_str(), last_url.size() + 1); + current_offset += url_data_size; + current_offset = align_offset(current_offset); + tlv_count++; + } + + // 0x3 LastUrlSize + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::LastURLSize; + tlv.arg_data_size = sizeof(u64); + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + const u64 url_size = last_url.size(); + std::memcpy(out_data.data() + current_offset, &url_size, sizeof(u64)); + current_offset += sizeof(u64); + tlv_count++; + } + + WebArgHeader out_header{}; + out_header.total_tlv_entries = tlv_count; + out_header.shim_kind = web_arg_header.shim_kind; + std::memcpy(out_data.data(), &out_header, sizeof(WebArgHeader)); + + LOG_DEBUG(Service_AM, "TLV output: total_size={}, tlv_count={}, used_offset={}", + out_data.size(), tlv_count, current_offset); + + complete = true; + PushOutData(std::make_shared(system, std::move(out_data))); + Exit(); + return; } - WebCommonReturnValue web_common_return_value; + // for old browser, keep old return, use WebCommonReturnValue + WebCommonReturnValue web_common_return_value{}; web_common_return_value.exit_reason = exit_reason; - std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size()); + std::memcpy(&web_common_return_value.last_url, last_url.data(), + (std::min)(last_url.size(), web_common_return_value.last_url.size())); web_common_return_value.last_url_size = last_url.size(); LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}", @@ -516,4 +617,13 @@ void WebBrowser::ExecuteLobby() { LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); WebBrowserExit(WebExitReason::EndButtonPressed); } + +void WebBrowser::InitializeLhub() {} + +void WebBrowser::ExecuteLhub() { + LOG_INFO(Service_AM, "(STUBBED) called, Lhub Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + + } // namespace Service::AM::Frontend diff --git a/src/core/hle/service/am/frontend/applet_web_browser.h b/src/core/hle/service/am/frontend/applet_web_browser.h index ba20b7a4cf..ae62389f13 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.h +++ b/src/core/hle/service/am/frontend/applet_web_browser.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -53,6 +56,7 @@ private: void InitializeWeb(); void InitializeWifi(); void InitializeLobby(); + void InitializeLhub(); // Executors for the various types of browser applets void ExecuteShop(); @@ -62,6 +66,7 @@ private: void ExecuteWeb(); void ExecuteWifi(); void ExecuteLobby(); + void ExecuteLhub(); const Core::Frontend::WebBrowserApplet& frontend; diff --git a/src/core/hle/service/am/frontend/applet_web_browser_types.h b/src/core/hle/service/am/frontend/applet_web_browser_types.h index 2f7c05c243..0892a6a716 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser_types.h +++ b/src/core/hle/service/am/frontend/applet_web_browser_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -30,6 +33,7 @@ enum class ShimKind : u32 { Web = 5, Wifi = 6, Lobby = 7, + Lhub = 8, }; enum class WebExitReason : u32 { diff --git a/src/core/hle/service/am/frontend/applets.cpp b/src/core/hle/service/am/frontend/applets.cpp index a25b7e3aa2..2ff2bcdbb6 100644 --- a/src/core/hle/service/am/frontend/applets.cpp +++ b/src/core/hle/service/am/frontend/applets.cpp @@ -237,13 +237,15 @@ std::shared_ptr FrontendAppletHolder::GetApplet(std::shared_ptr< case AppletId::OfflineWeb: case AppletId::LoginShare: case AppletId::WebAuth: + case AppletId::Lhub: return std::make_shared(system, applet, mode, *frontend.web_browser); case AppletId::PhotoViewer: return std::make_shared(system, applet, mode, *frontend.photo_viewer); case AppletId::NetConnect: return std::make_shared(system, applet, mode, *frontend.net_connect); default: - LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X}. Falling back to stub applet", static_cast(id)); + LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X} program_id={:016X}" + "Falling back to stub applet", static_cast(id), applet->program_id); return std::make_shared(system, applet, id, mode); } } diff --git a/src/core/hle/service/am/service/application_creator.cpp b/src/core/hle/service/am/service/application_creator.cpp index 2fc33a303c..d16fd7dd84 100644 --- a/src/core/hle/service/am/service/application_creator.cpp +++ b/src/core/hle/service/am/service/application_creator.cpp @@ -15,6 +15,7 @@ #include "core/hle/service/am/window_system.h" #include "core/hle/service/cmif_serialization.h" #include "core/loader/loader.h" +#include "core/launch_timestamp_cache.h" namespace Service::AM { @@ -72,6 +73,7 @@ IApplicationCreator::~IApplicationCreator() = default; Result IApplicationCreator::CreateApplication( Out> out_application_accessor, u64 application_id) { LOG_INFO(Service_NS, "called, application_id={:016X}", application_id); + Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id); R_RETURN( CreateGuestApplication(out_application_accessor, system, m_window_system, application_id)); } @@ -103,6 +105,7 @@ Result IApplicationCreator::CreateSystemApplication( *out_application_accessor = std::make_shared(system, applet, m_window_system); + Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/home_menu_functions.cpp b/src/core/hle/service/am/service/home_menu_functions.cpp index a0b05f6f36..f9d402ca99 100644 --- a/src/core/hle/service/am/service/home_menu_functions.cpp +++ b/src/core/hle/service/am/service/home_menu_functions.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -27,7 +27,7 @@ IHomeMenuFunctions::IHomeMenuFunctions(Core::System& system_, std::shared_ptr, "GetPopFromGeneralChannelEvent"}, {30, nullptr, "GetHomeButtonWriterLockAccessor"}, {31, nullptr, "GetWriterLockAccessorEx"}, - {40, nullptr, "IsSleepEnabled"}, + {40, D<&IHomeMenuFunctions::IsSleepEnabled>, "IsSleepEnabled"}, {41, D<&IHomeMenuFunctions::IsRebootEnabled>, "IsRebootEnabled"}, {50, nullptr, "LaunchSystemApplet"}, {51, nullptr, "LaunchStarter"}, @@ -80,6 +80,12 @@ Result IHomeMenuFunctions::GetPopFromGeneralChannelEvent( R_SUCCEED(); } +Result IHomeMenuFunctions::IsSleepEnabled(Out out_is_sleep_enbaled) { + LOG_INFO(Service_AM, "called"); + *out_is_sleep_enbaled = false; + R_SUCCEED(); +} + Result IHomeMenuFunctions::IsRebootEnabled(Out out_is_reboot_enbaled) { LOG_INFO(Service_AM, "called"); *out_is_reboot_enbaled = true; diff --git a/src/core/hle/service/am/service/home_menu_functions.h b/src/core/hle/service/am/service/home_menu_functions.h index 140071a74e..2c3ef54375 100644 --- a/src/core/hle/service/am/service/home_menu_functions.h +++ b/src/core/hle/service/am/service/home_menu_functions.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -29,6 +29,7 @@ private: Result UnlockForeground(); Result PopFromGeneralChannel(Out> out_storage); Result GetPopFromGeneralChannelEvent(OutCopyHandle out_event); + Result IsSleepEnabled(Out out_is_sleep_enbaled); Result IsRebootEnabled(Out out_is_reboot_enbaled); Result IsForceTerminateApplicationDisabledForDebug( Out out_is_force_terminate_application_disabled_for_debug); diff --git a/src/core/hle/service/am/service/library_applet_accessor.cpp b/src/core/hle/service/am/service/library_applet_accessor.cpp index 6b31fdb73c..c5b6929ab3 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_accessor.cpp @@ -1,18 +1,54 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/settings.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_data_broker.h" #include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/frontend/applet_profile_select.h" #include "core/hle/service/am/frontend/applets.h" +#include "core/hle/service/am/library_applet_storage.h" #include "core/hle/service/am/service/library_applet_accessor.h" #include "core/hle/service/am/service/storage.h" #include "core/hle/service/cmif_serialization.h" namespace Service::AM { +namespace { + +void EnableSingleUserPlay(const std::shared_ptr& impl) { + constexpr s64 DisplayOptionsOffset = 0x90; + constexpr s64 IsSkipEnabledOffset = 1; + constexpr s64 ShowSkipButtonOffset = 4; + constexpr bool enabled = true; + + impl->Write(DisplayOptionsOffset + IsSkipEnabledOffset, &enabled, sizeof(enabled)); + impl->Write(DisplayOptionsOffset + ShowSkipButtonOffset, &enabled, sizeof(enabled)); +} + +void ReplaceEmptyUuidWithCurrentUser(const std::shared_ptr& impl) { + Frontend::UiReturnArg return_arg{}; + impl->Read(0, &return_arg, sizeof(return_arg)); + + if (return_arg.uuid_selected.IsValid()) { + return; + } + + Service::Account::ProfileManager profile_manager; + const auto current_user_idx = Settings::values.current_user.GetValue(); + + if (auto uuid = profile_manager.GetUser(current_user_idx)) { + return_arg.result = 0; + return_arg.uuid_selected = *uuid; + impl->Write(0, &return_arg, sizeof(return_arg)); + } +} + +} // namespace + ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_, std::shared_ptr broker, std::shared_ptr applet) @@ -106,19 +142,45 @@ Result ILibraryAppletAccessor::Unknown90() { Result ILibraryAppletAccessor::PushInData(SharedPointer storage) { LOG_DEBUG(Service_AM, "called"); + + // Special case for ProfileSelect applet, to enable single user play as + // somehow some games want an additional user and not let you continue... + if (m_applet->applet_id == AppletId::ProfileSelect) { + auto impl = storage->GetImpl(); + const s64 size = impl->GetSize(); + + const bool is_ui_settings = size == sizeof(Frontend::UiSettings) || + size == sizeof(Frontend::UiSettingsV1); + if (is_ui_settings) { + EnableSingleUserPlay(impl); + } + } + m_broker->GetInData().Push(storage); R_SUCCEED(); } Result ILibraryAppletAccessor::PopOutData(Out> out_storage) { LOG_DEBUG(Service_AM, "called"); + if (auto caller_applet = m_applet->caller_applet.lock(); caller_applet) { caller_applet->lifecycle_manager.GetSystemEvent().Signal(); caller_applet->lifecycle_manager.RequestResumeNotification(); caller_applet->lifecycle_manager.GetSystemEvent().Clear(); caller_applet->lifecycle_manager.UpdateRequestedFocusState(); } - R_RETURN(m_broker->GetOutData().Pop(out_storage.Get())); + + R_TRY(m_broker->GetOutData().Pop(out_storage.Get())); + + if (m_applet->applet_id == AppletId::ProfileSelect && *out_storage) { + auto impl = (*out_storage)->GetImpl(); + + if (impl->GetSize() == sizeof(Frontend::UiReturnArg)) { + ReplaceEmptyUuidWithCurrentUser(impl); + } + } + + R_SUCCEED(); } Result ILibraryAppletAccessor::PushInteractiveInData(SharedPointer storage) { diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.cpp b/src/core/hle/service/am/service/library_applet_self_accessor.cpp index 4d36d306bd..0f71f24b0b 100644 --- a/src/core/hle/service/am/service/library_applet_self_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_self_accessor.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -83,7 +83,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_, {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, {140, nullptr, "SetApplicationMemoryReservation"}, {150, D<&ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually>, "ShouldSetGpuTimeSliceManually"}, - {160, D<&ILibraryAppletSelfAccessor::Cmd160>, "Cmd160"}, + {160, D<&ILibraryAppletSelfAccessor::GetLibraryAppletInfoEx>, "GetLibraryAppletInfoEx"}, }; // clang-format on RegisterHandlers(functions); @@ -323,9 +323,13 @@ Result ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually( R_SUCCEED(); } -Result ILibraryAppletSelfAccessor::Cmd160(Out out_unknown0) { - LOG_WARNING(Service_AM, "(STUBBED) called"); - *out_unknown0 = 0; +Result ILibraryAppletSelfAccessor::GetLibraryAppletInfoEx( + Out out_library_applet_info) { + LOG_INFO(Service_AM, "called"); + *out_library_applet_info = { + .applet_id = m_applet->applet_id, + .library_applet_mode = m_applet->library_applet_mode, + }; R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.h b/src/core/hle/service/am/service/library_applet_self_accessor.h index 8c850faa8e..78864a94eb 100644 --- a/src/core/hle/service/am/service/library_applet_self_accessor.h +++ b/src/core/hle/service/am/service/library_applet_self_accessor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -75,7 +78,7 @@ private: Result GetMainAppletAvailableUsers(Out out_can_select_any_user, Out out_users_count, OutArray out_users); Result ShouldSetGpuTimeSliceManually(Out out_should_set_gpu_time_slice_manually); - Result Cmd160(Out out_unknown0); + Result GetLibraryAppletInfoEx(Out out_library_applet_info); const std::shared_ptr m_applet; const std::shared_ptr m_broker; diff --git a/src/core/hle/service/bcat/news/builtin_news.cpp b/src/core/hle/service/bcat/news/builtin_news.cpp new file mode 100644 index 0000000000..2c93da71e0 --- /dev/null +++ b/src/core/hle/service/bcat/news/builtin_news.cpp @@ -0,0 +1,554 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/bcat/news/builtin_news.h" +#include "core/hle/service/bcat/news/msgpack.h" +#include "core/hle/service/bcat/news/news_storage.h" + +#include "common/fs/file.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef YUZU_BUNDLED_OPENSSL +#include +#endif + +namespace Service::News { +namespace { + +constexpr const char* GitHubAPI_EdenReleases = "/repos/eden-emulator/Releases/releases"; + +// Cached logo data +std::vector default_logo_small; +std::vector default_logo_large; +bool default_logos_loaded = false; + +std::unordered_map> news_images_small; +std::unordered_map> news_images_large; +std::mutex images_mutex; + + +std::filesystem::path GetCachePath() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "news" / "github_releases.json"; +} + +std::filesystem::path GetDefaultLogoPath(bool large) { + return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "news" / + (large ? "eden_logo_large.jpg" : "eden_logo_small.jpg"); +} + +std::filesystem::path GetNewsImagePath(std::string_view news_id, bool large) { + const std::string filename = fmt::format("{}_{}.jpg", news_id, large ? "large" : "small"); + return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "news" / "images" / filename; +} + +u32 HashToNewsId(std::string_view key) { + return static_cast(std::hash{}(key) & 0x7FFFFFFF); +} + +u64 ParseIsoTimestamp(const std::string& iso) { + if (iso.empty()) return 0; + + std::string buf = iso; + if (buf.back() == 'Z') buf.pop_back(); + + std::tm tm{}; + std::istringstream ss(buf); + ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"); + if (ss.fail()) return 0; + +#ifdef _WIN32 + return static_cast(_mkgmtime(&tm)); +#else + return static_cast(timegm(&tm)); +#endif +} + +std::vector TryLoadFromDisk(const std::filesystem::path& path) { + if (!std::filesystem::exists(path)) return {}; + + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) return {}; + + const auto file_size = static_cast(f.tellg()); + if (file_size <= 0 || file_size > 10 * 1024 * 1024) return {}; + + f.seekg(0); + std::vector data(static_cast(file_size)); + if (!f.read(reinterpret_cast(data.data()), file_size)) return {}; + + return data; +} + +std::vector DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) { + LOG_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + try { + httplib::Client cli("https://eden-emu.dev"); + cli.set_follow_location(true); + cli.set_connection_timeout(std::chrono::seconds(2)); + cli.set_read_timeout(std::chrono::seconds(2)); + +#ifdef YUZU_BUNDLED_OPENSSL + cli.load_ca_cert_store(kCert, sizeof(kCert)); +#endif + + if (auto res = cli.Get(url_path); res && res->status == 200 && !res->body.empty()) { + std::vector data(res->body.begin(), res->body.end()); + + std::error_code ec; + std::filesystem::create_directories(cache_path.parent_path(), ec); + if (std::ofstream out(cache_path, std::ios::binary); out) { + out.write(res->body.data(), static_cast(res->body.size())); + } + return data; + } + } catch (...) { + LOG_WARNING(Service_BCAT, "Failed to download: {}", url_path); + } +#endif + + return {}; +} + +std::vector LoadDefaultLogo(bool large) { + const auto path = GetDefaultLogoPath(large); + const std::string url = large ? "/news/eden_logo_large.jpg" : "/news/eden_logo_small.jpg"; + + auto data = TryLoadFromDisk(path); + if (!data.empty()) return data; + + return DownloadImage(url, path); +} + +void LoadDefaultLogos() { + if (default_logos_loaded) return; + default_logos_loaded = true; + + default_logo_small = LoadDefaultLogo(false); + default_logo_large = LoadDefaultLogo(true); +} + +std::vector GetNewsImage(std::string_view news_id, bool large) { + const std::string id_str{news_id}; + + { + std::lock_guard lock{images_mutex}; + auto& cache = large ? news_images_large : news_images_small; + if (auto it = cache.find(id_str); it != cache.end()) { + return it->second; + } + } + + const auto cache_path = GetNewsImagePath(news_id, large); + auto data = TryLoadFromDisk(cache_path); + + if (data.empty()) { + const std::string url = fmt::format("/news/{}_{}.jpg", id_str, large ? "large" : "small"); + data = DownloadImage(url, cache_path); + } + + if (data.empty()) { + data = large ? default_logo_large : default_logo_small; + } + + { + std::lock_guard lock{images_mutex}; + auto& cache = large ? news_images_large : news_images_small; + cache[id_str] = data; + } + + return data; +} + +void PreloadNewsImages(const std::vector& news_ids) { + std::vector> futures; + futures.reserve(news_ids.size() * 2); + + for (const u32 id : news_ids) { + const std::string id_str = fmt::format("{}", id); + + { + std::lock_guard lock{images_mutex}; + if (news_images_small.contains(id_str) && news_images_large.contains(id_str)) { + continue; + } + } + + const auto path_small = GetNewsImagePath(id_str, false); + const auto path_large = GetNewsImagePath(id_str, true); + if (std::filesystem::exists(path_small) && std::filesystem::exists(path_large)) { + continue; + } + + futures.push_back(std::async(std::launch::async, [id_str]() { + GetNewsImage(id_str, false); + })); + futures.push_back(std::async(std::launch::async, [id_str]() { + GetNewsImage(id_str, true); + })); + } + + for (auto& f : futures) { + f.wait(); + } +} + +std::optional ReadCachedJson() { + const auto path = GetCachePath(); + if (!std::filesystem::exists(path)) return std::nullopt; + + auto content = Common::FS::ReadStringFromFile(path, Common::FS::FileType::TextFile); + return content.empty() ? std::nullopt : std::optional{std::move(content)}; +} + +void WriteCachedJson(std::string_view json) { + const auto path = GetCachePath(); + std::error_code ec; + std::filesystem::create_directories(path.parent_path(), ec); + (void)Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, json); +} + +std::optional DownloadReleasesJson() { + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + try { + httplib::SSLClient cli{"api.github.com", 443}; + cli.set_connection_timeout(10); + cli.set_read_timeout(10); + + httplib::Headers headers{ + {"User-Agent", "Eden"}, + {"Accept", "application/vnd.github+json"}, + }; + + // TODO(crueter): automate this in some way... +#ifdef YUZU_BUNDLED_OPENSSL + cli.load_ca_cert_store(kCert, sizeof(kCert)); +#endif + + if (auto res = cli.Get(GitHubAPI_EdenReleases, headers); res && res->status < 400) { + return res->body; + } + } catch (...) { + LOG_WARNING(Service_BCAT, " failed to download releases"); + } +#endif + return std::nullopt; +} + +// idk but News App does not render Markdown or HTML, so remove some formatting. +std::string SanitizeMarkdown(std::string_view markdown) { + std::string result; + result.reserve(markdown.size()); + + // our current structure for markdown is after "# Packages" remove everything. + std::string text{markdown}; + if (auto pos = text.find("# Packages"); pos != std::string::npos) { + text = text.substr(0, pos); + } + + // Fix line endings + boost::replace_all(text, "\r", ""); + + // Remove backticks + boost::replace_all(text, "`", ""); + + // Remove excessive newlines + static const boost::regex newlines(R"(\n\n\n+)"); + text = boost::regex_replace(text, newlines, "\n\n"); + + // Remove markdown headers + static const boost::regex headers(R"(^#+ )"); + text = boost::regex_replace(text, headers, ""); + + // Convert bullet points to something nicer + static const boost::regex list1(R"(^- )"); + text = boost::regex_replace(text, list1, "• "); + + static const boost::regex list2(R"(^ \* )"); + text = boost::regex_replace(text, list2, " • "); + + // what + static const boost::regex list2_dash(R"(^ - )"); + text = boost::regex_replace(text, list2_dash, " • "); + + // Convert bold/italic text into normal text + static const boost::regex bold(R"(\*\*(.*?)\*\*)"); + text = boost::regex_replace(text, bold, "$1"); + + static const boost::regex italic(R"(\*(.*?)\*)"); + text = boost::regex_replace(text, italic, "$1"); + + // Remove links and convert to normal text + static const boost::regex link(R"(\[([^\]]+)\]\([^)]*\))"); + text = boost::regex_replace(text, link, "$1"); + + // Trim trailing whitespace/newlines + while (!text.empty() && (text.back() == '\n' || text.back() == ' ')) { + text.pop_back(); + } + + return text; +} + +std::string FormatBody(const nlohmann::json& release, std::string_view title) { + std::string body = release.value("body", std::string{}); + + if (body.empty()) { + return std::string(title); + } + + // Sanitize markdown + body = SanitizeMarkdown(body); + + // Limit body length - News app has character limits + size_t max_body_length = 4000; + if (body.size() > max_body_length) { + size_t cut_pos = body.rfind('\n', max_body_length); + if (cut_pos == std::string::npos || cut_pos < max_body_length / 2) { + cut_pos = body.rfind(". ", max_body_length); + } + if (cut_pos == std::string::npos || cut_pos < max_body_length / 2) { + cut_pos = max_body_length; + } + body = body.substr(0, cut_pos); + + // Trim trailing whitespace + while (!body.empty() && (body.back() == '\n' || body.back() == ' ')) { + body.pop_back(); + } + + body += "\n\n... View more on GitHub"; + } + + return body; +} + +void ImportReleases(std::string_view json_text) { + nlohmann::json root; + try { + root = nlohmann::json::parse(json_text); + } catch (...) { + LOG_WARNING(Service_BCAT, "failed to parse JSON"); + return; + } + + if (!root.is_array()) return; + + std::vector news_ids; + for (const auto& rel : root) { + if (!rel.is_object()) continue; + std::string title = rel.value("name", rel.value("tag_name", std::string{})); + if (title.empty()) continue; + + const u64 release_id = rel.value("id", 0); + const u32 news_id = release_id ? static_cast(release_id & 0x7FFFFFFF) : HashToNewsId(title); + news_ids.push_back(news_id); + } + + PreloadNewsImages(news_ids); + + for (const auto& rel : root) { + if (!rel.is_object()) continue; + + std::string title = rel.value("name", rel.value("tag_name", std::string{})); + if (title.empty()) continue; + + const u64 release_id = rel.value("id", 0); + const u32 news_id = release_id ? static_cast(release_id & 0x7FFFFFFF) : HashToNewsId(title); + const u64 published = ParseIsoTimestamp(rel.value("published_at", std::string{})); + const u64 pickup_limit = published + 600000000; + const u32 priority = rel.value("prerelease", false) ? 1500 : 2500; + + std::string author = "eden"; + if (rel.contains("author") && rel["author"].is_object()) { + author = rel["author"].value("login", "eden"); + } + + auto payload = BuildMsgpack(title, FormatBody(rel, title), title, published, + pickup_limit, priority, {"en"}, author, {}, + rel.value("html_url", std::string{}), news_id); + + const std::string news_id_str = fmt::format("LA{:020}", news_id); + + GithubNewsMeta meta{ + .news_id = news_id_str, + .topic_id = "1", + .published_at = published, + .pickup_limit = pickup_limit, + .essential_pickup_limit = pickup_limit, + .expire_at = 0, + .priority = priority, + .deletion_priority = 100, + .decoration_type = 1, + .opted_in = 1, + .essential_pickup_limit_flag = 1, + .category = 0, + .language_mask = 1, + }; + + NewsStorage::Instance().UpsertRaw(meta, std::move(payload)); + } +} + +} // anonymous namespace + +std::vector BuildMsgpack(std::string_view title, std::string_view body, + std::string_view topic_name, u64 published_at, + u64 pickup_limit, u32 priority, + const std::vector& languages, + const std::string& author, + const std::vector>& /*assets*/, + const std::string& html_url, + std::optional override_id) { + MsgPack::Writer w; + + const u32 news_id = override_id.value_or(HashToNewsId(title.empty() ? "eden" : title)); + const std::string news_id_str = fmt::format("{}", news_id); + + const auto img_small = GetNewsImage(news_id_str, false); + const auto img_large = GetNewsImage(news_id_str, true); + + w.WriteFixMap(23); + + // Version infos, could exist a 2? + w.WriteKey("version"); + w.WriteFixMap(2); + w.WriteKey("format"); + w.WriteUInt(1); + w.WriteKey("semantics"); + w.WriteUInt(1); + + // Metadata + w.WriteKey("news_id"); + w.WriteUInt(news_id); + w.WriteKey("published_at"); + w.WriteUInt(published_at); + w.WriteKey("pickup_limit"); + w.WriteUInt(pickup_limit); + w.WriteKey("priority"); + w.WriteUInt(priority); + w.WriteKey("deletion_priority"); + w.WriteUInt(100); + + // Language + w.WriteKey("language"); + w.WriteString(languages.empty() ? "en" : languages.front()); + w.WriteKey("supported_languages"); + w.WriteFixArray(languages.size()); + for (const auto& lang : languages) w.WriteString(lang); + + // Display settings + w.WriteKey("display_type"); + w.WriteString("NORMAL"); + w.WriteKey("topic_id"); + w.WriteString("eden"); + + w.WriteKey("no_photography"); // still show image + w.WriteUInt(0); + w.WriteKey("surprise"); // no idea + w.WriteUInt(0); + w.WriteKey("bashotorya"); // no idea + w.WriteUInt(0); + w.WriteKey("movie"); + w.WriteUInt(0); // 1 = has video, movie_url must be set but we don't support it yet + + // News Subject (Title) + w.WriteKey("subject"); + w.WriteFixMap(2); + w.WriteKey("caption"); + w.WriteUInt(1); + w.WriteKey("text"); + w.WriteString(title.empty() ? "No title" : title); + + // Topic name = who wrote it + w.WriteKey("topic_name"); + w.WriteString("Eden"); + + w.WriteKey("list_image"); + w.WriteBinary(img_small); + + // Footer + w.WriteKey("footer"); + w.WriteFixMap(1); + w.WriteKey("text"); + w.WriteString(""); + + w.WriteKey("allow_domains"); + w.WriteString("^https?://github.com(/|$)"); + + // More link + w.WriteKey("more"); + w.WriteFixMap(1); + w.WriteKey("browser"); + w.WriteFixMap(2); + w.WriteKey("url"); + w.WriteString(html_url); + w.WriteKey("text"); + w.WriteString("Open GitHub"); + + // Body + w.WriteKey("body"); + w.WriteFixMap(4); + w.WriteKey("text"); + w.WriteString(body); + w.WriteKey("main_image_height"); + w.WriteUInt(450); + w.WriteKey("movie_url"); + w.WriteString(""); + w.WriteKey("main_image"); + w.WriteBinary(img_large); + + // no clue + w.WriteKey("contents_descriptors"); + w.WriteString(""); + w.WriteKey("interactive_elements"); + w.WriteString(""); + + return w.Take(); +} + +void EnsureBuiltinNewsLoaded() { + static std::once_flag once; + std::call_once(once, [] { + LoadDefaultLogos(); + + if (const auto cached = ReadCachedJson()) { + ImportReleases(*cached); + LOG_DEBUG(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size()); + } + + std::thread([] { + if (const auto fresh = DownloadReleasesJson()) { + WriteCachedJson(*fresh); + ImportReleases(*fresh); + LOG_DEBUG(Service_BCAT, "news: {} entries updated from GitHub", NewsStorage::Instance().ListAll().size()); + } + }).detach(); + }); +} + +} // namespace Service::News diff --git a/src/core/hle/service/bcat/news/builtin_news.h b/src/core/hle/service/bcat/news/builtin_news.h new file mode 100644 index 0000000000..cb06b2d4e3 --- /dev/null +++ b/src/core/hle/service/bcat/news/builtin_news.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include +#include +#include +#include + +namespace Service::News { + +void EnsureBuiltinNewsLoaded(); + + std::vector BuildMsgpack(std::string_view title, std::string_view body, + std::string_view topic_name, + u64 published_at, + u64 pickup_limit, + u32 priority, + const std::vector& languages, + const std::string& author_name, + const std::vector>& assets, + const std::string& html_url, + std::optional override_news_id = std::nullopt); + +} // namespace Service::News diff --git a/src/core/hle/service/bcat/news/msgpack.cpp b/src/core/hle/service/bcat/news/msgpack.cpp new file mode 100644 index 0000000000..b67b75ea9d --- /dev/null +++ b/src/core/hle/service/bcat/news/msgpack.cpp @@ -0,0 +1,886 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/bcat/news/msgpack.h" + +#include +#include + +// This file is a partial MsgPack implementation, only implementing the features +// needed for the News service. Can be extended but enough for the news use case. + +namespace Service::News { + +void MsgPack::Writer::WriteBytes(std::span bytes) { + out.insert(out.end(), bytes.begin(), bytes.end()); +} + +void MsgPack::Writer::WriteFixMap(size_t count) { + if (count <= 15) { + out.push_back(static_cast(0x80 | count)); + } else if (count <= 0xFFFF) { + out.push_back(0xDE); + out.push_back(static_cast((count >> 8) & 0xFF)); + out.push_back(static_cast(count & 0xFF)); + } else { + WriteMap32(count); + } +} + +void MsgPack::Writer::WriteMap32(size_t count) { + out.push_back(0xDF); + out.push_back(static_cast((count >> 24) & 0xFF)); + out.push_back(static_cast((count >> 16) & 0xFF)); + out.push_back(static_cast((count >> 8) & 0xFF)); + out.push_back(static_cast(count & 0xFF)); +} + +void MsgPack::Writer::WriteKey(std::string_view s) { + WriteString(s); +} + +void MsgPack::Writer::WriteString(std::string_view s) { + if (s.size() <= 31) { + out.push_back(static_cast(0xA0 | s.size())); + } else if (s.size() <= 0xFF) { + out.push_back(0xD9); + out.push_back(static_cast(s.size())); + } else if (s.size() <= 0xFFFF) { + out.push_back(0xDA); + out.push_back(static_cast((s.size() >> 8) & 0xFF)); + out.push_back(static_cast(s.size() & 0xFF)); + } else { + out.push_back(0xDB); + out.push_back(static_cast((s.size() >> 24) & 0xFF)); + out.push_back(static_cast((s.size() >> 16) & 0xFF)); + out.push_back(static_cast((s.size() >> 8) & 0xFF)); + out.push_back(static_cast(s.size() & 0xFF)); + } + WriteBytes({reinterpret_cast(s.data()), s.size()}); +} + +void MsgPack::Writer::WriteInt64(s64 v) { + if (v >= 0) { + WriteUInt(static_cast(v)); + return; + } + if (v >= -32) { + out.push_back(static_cast(0xE0 | (v + 32))); + } else if (v >= -128) { + out.push_back(0xD0); + out.push_back(static_cast(v & 0xFF)); + } else if (v >= -32768) { + out.push_back(0xD1); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast(v & 0xFF)); + } else if (v >= INT32_MIN) { + out.push_back(0xD2); + out.push_back(static_cast((v >> 24) & 0xFF)); + out.push_back(static_cast((v >> 16) & 0xFF)); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast(v & 0xFF)); + } else { + out.push_back(0xD3); + for (int i = 7; i >= 0; --i) { + out.push_back(static_cast((static_cast(v) >> (8 * i)) & 0xFF)); + } + } +} + +void MsgPack::Writer::WriteUInt(u64 v) { + if (v < 0x80) { + out.push_back(static_cast(v)); + } else if (v <= 0xFF) { + out.push_back(0xCC); + out.push_back(static_cast(v)); + } else if (v <= 0xFFFF) { + out.push_back(0xCD); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast(v & 0xFF)); + } else if (v <= 0xFFFFFFFFULL) { + out.push_back(0xCE); + out.push_back(static_cast((v >> 24) & 0xFF)); + out.push_back(static_cast((v >> 16) & 0xFF)); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast(v & 0xFF)); + } else { + out.push_back(0xCF); + for (int i = 7; i >= 0; --i) { + out.push_back(static_cast((v >> (8 * i)) & 0xFF)); + } + } +} + +void MsgPack::Writer::WriteNil() { + out.push_back(0xC0); +} + +void MsgPack::Writer::WriteFixArray(size_t count) { + if (count <= 15) { + out.push_back(static_cast(0x90 | count)); + } else if (count <= 0xFFFF) { + out.push_back(0xDC); + out.push_back(static_cast((count >> 8) & 0xFF)); + out.push_back(static_cast(count & 0xFF)); + } else { + out.push_back(0xDD); + out.push_back(static_cast((count >> 24) & 0xFF)); + out.push_back(static_cast((count >> 16) & 0xFF)); + out.push_back(static_cast((count >> 8) & 0xFF)); + out.push_back(static_cast(count & 0xFF)); + } +} + +void MsgPack::Writer::WriteBinary(const std::vector& data) { + if (data.size() <= 0xFF) { + out.push_back(0xC4); + out.push_back(static_cast(data.size())); + } else if (data.size() <= 0xFFFF) { + out.push_back(0xC5); + out.push_back(static_cast((data.size() >> 8) & 0xFF)); + out.push_back(static_cast(data.size() & 0xFF)); + } else { + out.push_back(0xC6); + out.push_back(static_cast((data.size() >> 24) & 0xFF)); + out.push_back(static_cast((data.size() >> 16) & 0xFF)); + out.push_back(static_cast((data.size() >> 8) & 0xFF)); + out.push_back(static_cast(data.size() & 0xFF)); + } + WriteBytes(data); +} + +void MsgPack::Writer::WriteBool(bool v) { + out.push_back(v ? 0xC3 : 0xC2); +} + +std::vector MsgPack::Writer::Take() { + return std::move(out); +} + +MsgPack::Reader::Reader(std::span buffer) : data(buffer) {} + +bool MsgPack::Reader::Fail(const char* msg) { + error = msg; + return false; +} + +bool MsgPack::Reader::Ensure(size_t n) const { + return offset + n <= data.size(); +} + +u8 MsgPack::Reader::Peek() const { + return Ensure(1) ? data[offset] : 0; +} + +u8 MsgPack::Reader::ReadByte() { + if (!Ensure(1)) { + return 0; + } + return data[offset++]; +} + +bool MsgPack::Reader::ReadSize(size_t byte_count, size_t& out_size) { + if (!Ensure(byte_count)) { + return Fail("size out of range"); + } + size_t value = 0; + for (size_t i = 0; i < byte_count; ++i) { + value = (value << 8) | data[offset + i]; + } + offset += byte_count; + out_size = value; + return true; +} + +bool MsgPack::Reader::SkipBytes(size_t n) { + if (!Ensure(n)) { + return Fail("skip out of range"); + } + offset += n; + return true; +} + +bool MsgPack::Reader::SkipValue() { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = ReadByte(); + + if (byte <= 0x7F || (byte >= 0xE0)) { + return true; + } + if ((byte & 0xE0) == 0xA0) { + const size_t len = byte & 0x1F; + return SkipBytes(len); + } + if ((byte & 0xF0) == 0x80) { + const size_t count = byte & 0x0F; + return SkipContainer(count * 2, true); + } + if ((byte & 0xF0) == 0x90) { + const size_t count = byte & 0x0F; + return SkipContainer(count, false); + } + + switch (byte) { + case 0xC0: + case 0xC2: + case 0xC3: + return true; + case 0xC4: + case 0xC5: + case 0xC6: { + size_t size = 0; + if (!ReadSize(static_cast(1) << (byte - 0xC4), size)) { + return false; + } + return SkipBytes(size); + } + case 0xC7: + case 0xC8: + case 0xC9: + return Fail("ext not supported"); + case 0xCA: + return SkipBytes(4); + case 0xCB: + return SkipBytes(8); + case 0xCC: + return SkipBytes(1); + case 0xCD: + return SkipBytes(2); + case 0xCE: + return SkipBytes(4); + case 0xCF: + return SkipBytes(8); + case 0xD0: + return SkipBytes(1); + case 0xD1: + return SkipBytes(2); + case 0xD2: + return SkipBytes(4); + case 0xD3: + return SkipBytes(8); + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + return Fail("fixext not supported"); + case 0xD9: { + size_t size = 0; + if (!ReadSize(1, size)) { + return false; + } + return SkipBytes(size); + } + case 0xDA: { + size_t size = 0; + if (!ReadSize(2, size)) { + return false; + } + return SkipBytes(size); + } + case 0xDB: { + size_t size = 0; + if (!ReadSize(4, size)) { + return false; + } + return SkipBytes(size); + } + case 0xDC: { + size_t count = 0; + if (!ReadSize(2, count)) { + return false; + } + return SkipContainer(count, false); + } + case 0xDD: { + size_t count = 0; + if (!ReadSize(4, count)) { + return false; + } + return SkipContainer(count, false); + } + case 0xDE: { + size_t count = 0; + if (!ReadSize(2, count)) { + return false; + } + return SkipContainer(count * 2, true); + } + case 0xDF: { + size_t count = 0; + if (!ReadSize(4, count)) { + return false; + } + return SkipContainer(count * 2, true); + } + default: + return Fail("unknown type"); + } +} + +bool MsgPack::Reader::SkipContainer(size_t count, bool /*map_mode*/) { + for (size_t i = 0; i < count; ++i) { + if (!SkipValue()) { + return false; + } + } + return true; +} + +bool MsgPack::Reader::SkipAll() { + while (!End()) { + if (!SkipValue()) { + return false; + } + } + return true; +} + +bool MsgPack::Reader::ReadMapHeader(size_t& count) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = Peek(); + if ((byte & 0xF0) == 0x80) { + count = byte & 0x0F; + offset++; + return true; + } + if (byte == 0xDE) { + ReadByte(); + return ReadSize(2, count); + } + if (byte == 0xDF) { + ReadByte(); + return ReadSize(4, count); + } + return Fail("not a map"); +} + +bool MsgPack::Reader::ReadArrayHeader(size_t& count) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = Peek(); + if ((byte & 0xF0) == 0x90) { + count = byte & 0x0F; + offset++; + return true; + } + if (byte == 0xDC) { + ReadByte(); + return ReadSize(2, count); + } + if (byte == 0xDD) { + ReadByte(); + return ReadSize(4, count); + } + return Fail("not an array"); +} + +bool MsgPack::Reader::ReadUInt(u64& value) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = Peek(); + if (byte <= 0x7F) { + value = byte; + offset++; + return true; + } + if (byte == 0xCC) { + ReadByte(); + if (!Ensure(1)) { + return Fail("uint8 truncated"); + } + value = data[offset++]; + return true; + } + if (byte == 0xCD) { + ReadByte(); + size_t tmp = 0; + if (!ReadSize(2, tmp)) { + return false; + } + value = tmp; + return true; + } + if (byte == 0xCE) { + ReadByte(); + size_t tmp = 0; + if (!ReadSize(4, tmp)) { + return false; + } + value = tmp; + return true; + } + if (byte == 0xCF) { + ReadByte(); + if (!Ensure(8)) { + return Fail("uint64 truncated"); + } + u64 tmp = 0; + for (int i = 0; i < 8; ++i) { + tmp = (tmp << 8) | data[offset + i]; + } + offset += 8; + value = tmp; + return true; + } + return Fail("not uint"); +} + +bool MsgPack::Reader::ReadInt(s64& value) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = Peek(); + if (byte <= 0x7F) { + value = byte; + offset++; + return true; + } + if (byte >= 0xE0) { + value = static_cast(byte); + offset++; + return true; + } + if (byte == 0xD0) { + ReadByte(); + if (!Ensure(1)) { + return Fail("int8 truncated"); + } + value = static_cast(data[offset]); + offset += 1; + return true; + } + if (byte == 0xD1) { + ReadByte(); + if (!Ensure(2)) { + return Fail("int16 truncated"); + } + value = static_cast((data[offset] << 8) | data[offset + 1]); + offset += 2; + return true; + } + if (byte == 0xD2) { + ReadByte(); + if (!Ensure(4)) { + return Fail("int32 truncated"); + } + const s32 tmp = static_cast((data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]); + offset += 4; + value = tmp; + return true; + } + if (byte == 0xD3) { + ReadByte(); + if (!Ensure(8)) { + return Fail("int64 truncated"); + } + s64 tmp = 0; + for (int i = 0; i < 8; ++i) { + tmp = (tmp << 8) | data[offset + i]; + } + offset += 8; + value = tmp; + return true; + } + return Fail("not int"); +} + +bool MsgPack::Reader::ReadBool(bool& value) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = ReadByte(); + if (byte == 0xC2) { + value = false; + return true; + } + if (byte == 0xC3) { + value = true; + return true; + } + return Fail("not bool"); +} + +bool MsgPack::Reader::ReadString(std::string& value) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = ReadByte(); + size_t len = 0; + if ((byte & 0xE0) == 0xA0) { + len = byte & 0x1F; + } else if (byte == 0xD9) { + if (!ReadSize(1, len)) { + return false; + } + } else if (byte == 0xDA) { + if (!ReadSize(2, len)) { + return false; + } + } else if (byte == 0xDB) { + if (!ReadSize(4, len)) { + return false; + } + } else { + return Fail("not string"); + } + + if (!Ensure(len)) { + return Fail("string truncated"); + } + value.assign(reinterpret_cast(data.data() + offset), len); + offset += len; + return true; +} + +bool MsgPack::Reader::ReadBinary(std::vector& value) { + if (End()) { + return Fail("unexpected end"); + } + const u8 byte = ReadByte(); + size_t len = 0; + if (byte == 0xC4) { + if (!ReadSize(1, len)) { + return false; + } + } else if (byte == 0xC5) { + if (!ReadSize(2, len)) { + return false; + } + } else if (byte == 0xC6) { + if (!ReadSize(4, len)) { + return false; + } + } else { + return Fail("not binary"); + } + if (!Ensure(len)) { + return Fail("binary truncated"); + } + value.assign(data.begin() + offset, data.begin() + offset + len); + offset += len; + return true; +} + +bool MsgPack::Reader::ReadBinaryCompat(std::vector& value) { + if (!ReadBinary(value)) { + value.clear(); + return false; + } + return true; +} + +bool MsgPack::Reader::ReadStringArray(std::vector& out) { + size_t count = 0; + if (!ReadArrayHeader(count)) { + return false; + } + out.clear(); + out.reserve(count); + for (size_t i = 0; i < count; ++i) { + std::string value; + if (!ReadString(value)) { + return false; + } + out.push_back(std::move(value)); + } + return true; +} + +bool MsgPack::Reader::ReadNewsVersion(NewsStruct::Version& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "format") { + if (!ReadUInt(out.format)) { + return false; + } + } else if (key == "semantics") { + if (!ReadUInt(out.semantics)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadNewsSubject(NewsStruct::Subject& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "caption") { + if (!ReadUInt(out.caption)) { + return false; + } + } else if (key == "text") { + if (!ReadString(out.text)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadNewsFooter(NewsStruct::Footer& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "text") { + if (!ReadString(out.text)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadByteArray(std::vector& out) { + if (!ReadBinary(out)) { + out.clear(); + return false; + } + return true; +} + +bool MsgPack::Reader::ReadNewsBody(NewsStruct::Body& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "text") { + if (!ReadString(out.text)) { + return false; + } + } else if (key == "main_image_height") { + if (!ReadUInt(out.main_image_height)) { + return false; + } + } else if (key == "movie_url") { + if (!ReadString(out.movie_url)) { + return false; + } + } else if (key == "main_image") { + if (!ReadByteArray(out.main_image)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadNewsBrowser(NewsStruct::More::Browser& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + out.present = true; + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "url") { + if (!ReadString(out.url)) { + return false; + } + } else if (key == "text") { + if (!ReadString(out.text)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadNewsMore(NewsStruct::More& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + out.has_browser = false; + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + if (key == "browser") { + out.has_browser = true; + if (!ReadNewsBrowser(out.browser)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +bool MsgPack::Reader::ReadNewsStruct(NewsStruct& out) { + size_t count = 0; + if (!ReadMapHeader(count)) { + return false; + } + for (size_t i = 0; i < count; ++i) { + std::string key; + if (!ReadString(key)) { + return false; + } + + auto read_u64 = [&](u64& target) -> bool { + return ReadUInt(target); + }; + + if (key == "version") { + if (!ReadNewsVersion(out.version)) { + return false; + } + } else if (key == "news_id") { + if (!read_u64(out.news_id)) { + return false; + } + } else if (key == "published_at") { + if (!read_u64(out.published_at)) { + return false; + } + } else if (key == "pickup_limit") { + if (!read_u64(out.pickup_limit)) { + return false; + } + } else if (key == "priority") { + if (!read_u64(out.priority)) { + return false; + } + } else if (key == "deletion_priority") { + if (!read_u64(out.deletion_priority)) { + return false; + } + } else if (key == "language") { + if (!ReadString(out.language)) { + return false; + } + } else if (key == "supported_languages") { + if (!ReadStringArray(out.supported_languages)) { + return false; + } + } else if (key == "display_type") { + if (!ReadString(out.display_type)) { + return false; + } + } else if (key == "topic_id") { + if (!ReadString(out.topic_id)) { + return false; + } + } else if (key == "no_photography") { + if (!read_u64(out.no_photography)) { + return false; + } + } else if (key == "surprise") { + if (!read_u64(out.surprise)) { + return false; + } + } else if (key == "bashotorya") { + if (!read_u64(out.bashotorya)) { + return false; + } + } else if (key == "movie") { + if (!read_u64(out.movie)) { + return false; + } + } else if (key == "subject") { + if (!ReadNewsSubject(out.subject)) { + return false; + } + } else if (key == "topic_name") { + if (!ReadString(out.topic_name)) { + return false; + } + } else if (key == "list_image") { + if (!ReadByteArray(out.list_image)) { + return false; + } + } else if (key == "footer") { + if (!ReadNewsFooter(out.footer)) { + return false; + } + } else if (key == "allow_domains") { + if (!ReadString(out.allow_domains)) { + return false; + } + } else if (key == "more") { + if (!ReadNewsMore(out.more)) { + return false; + } + } else if (key == "body") { + if (!ReadNewsBody(out.body)) { + return false; + } + } else if (key == "contents_descriptors") { + if (!ReadString(out.contents_descriptors)) { + return false; + } + } else if (key == "interactive_elements") { + if (!ReadString(out.interactive_elements)) { + return false; + } + } else { + if (!SkipValue()) { + return false; + } + } + } + return true; +} + +} // namespace Service::News diff --git a/src/core/hle/service/bcat/news/msgpack.h b/src/core/hle/service/bcat/news/msgpack.h new file mode 100644 index 0000000000..a7a2aa40ba --- /dev/null +++ b/src/core/hle/service/bcat/news/msgpack.h @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace Service::News { + +struct NewsStruct { + struct Version { + u64 format{}; + u64 semantics{}; + }; + + struct Subject { + u64 caption{}; + std::string text; + }; + + struct Footer { + std::string text; + }; + + struct Body { + std::string text; + u64 main_image_height{}; + std::string movie_url; + std::vector main_image; + }; + + struct More { + struct Browser { + std::string url; + std::string text; + bool present{}; + } browser; + bool has_browser{}; + }; + + Version version; + u64 news_id{}; + u64 published_at{}; + u64 pickup_limit{}; + u64 priority{}; + u64 deletion_priority{}; + std::string language; + std::vector supported_languages; + std::string display_type; + std::string topic_id; + u64 no_photography{}; + u64 surprise{}; + u64 bashotorya{}; + u64 movie{}; + Subject subject; + std::string topic_name; + std::vector list_image; + Footer footer; + std::string allow_domains; + More more; + Body body; + std::string contents_descriptors; + std::string interactive_elements; +}; + +class MsgPack { +public: + class Writer { + public: + void WriteFixMap(size_t count); + void WriteMap32(size_t count); + void WriteKey(std::string_view key); + void WriteString(std::string_view value); + void WriteInt64(s64 value); + void WriteUInt(u64 value); + void WriteNil(); + void WriteFixArray(size_t count); + void WriteBinary(const std::vector& data); + void WriteBool(bool value); + + std::vector Take(); + const std::vector& Buffer() const { return out; } + void Clear() { out.clear(); } + + private: + void WriteBytes(std::span bytes); + + std::vector out; + }; + + class Reader { + public: + explicit Reader(std::span buffer); + + template + bool Read(T& out) { + if constexpr (std::is_same_v) { + return ReadNewsStruct(out); + } else { + static_assert(sizeof(T) == 0, "Unsupported MsgPack::Reader::Read type"); + } + } + + bool SkipValue(); + bool SkipAll(); + + bool ReadMapHeader(size_t& count); + bool ReadArrayHeader(size_t& count); + bool ReadUInt(u64& value); + bool ReadInt(s64& value); + bool ReadBool(bool& value); + bool ReadString(std::string& value); + bool ReadBinary(std::vector& value); + + bool End() const { return offset >= data.size(); } + std::string_view Error() const { return error; } + + private: + bool Fail(const char* msg); + bool Ensure(size_t n) const; + u8 Peek() const; + u8 ReadByte(); + bool ReadSize(size_t byte_count, size_t& out_size); + bool SkipBytes(size_t n); + bool SkipContainer(size_t count, bool map_mode); + bool ReadNewsStruct(NewsStruct& out); + bool ReadNewsVersion(NewsStruct::Version& out); + bool ReadNewsSubject(NewsStruct::Subject& out); + bool ReadNewsFooter(NewsStruct::Footer& out); + bool ReadNewsBody(NewsStruct::Body& out); + bool ReadNewsMore(NewsStruct::More& out); + bool ReadNewsBrowser(NewsStruct::More::Browser& out); + bool ReadStringArray(std::vector& out); + bool ReadBinaryCompat(std::vector& out); + bool ReadByteArray(std::vector& out); + + std::span data; + size_t offset{0}; + std::string error; + }; +}; + +} // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_data_service.cpp b/src/core/hle/service/bcat/news/news_data_service.cpp index 08103c9c3b..c5d4b58881 100644 --- a/src/core/hle/service/bcat/news/news_data_service.cpp +++ b/src/core/hle/service/bcat/news/news_data_service.cpp @@ -1,25 +1,125 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/bcat/news/news_data_service.h" +#include "core/hle/service/bcat/news/builtin_news.h" +#include "core/hle/service/bcat/news/news_storage.h" +#include "core/hle/service/cmif_serialization.h" + +#include "common/logging/log.h" + +#include namespace Service::News { +namespace { + +std::string_view ToStringView(std::span buf) { + const std::string_view sv{buf.data(), buf.size()}; + const auto nul = sv.find('\0'); + return nul == std::string_view::npos ? sv : sv.substr(0, nul); +} + +} // namespace INewsDataService::INewsDataService(Core::System& system_) : ServiceFramework{system_, "INewsDataService"} { - // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Open"}, - {1, nullptr, "OpenWithNewsRecordV1"}, - {2, nullptr, "Read"}, - {3, nullptr, "GetSize"}, - {1001, nullptr, "OpenWithNewsRecord"}, + {0, D<&INewsDataService::Open>, "Open"}, + {1, D<&INewsDataService::OpenWithNewsRecordV1>, "OpenWithNewsRecordV1"}, + {2, D<&INewsDataService::Read>, "Read"}, + {3, D<&INewsDataService::GetSize>, "GetSize"}, + {1001, D<&INewsDataService::OpenWithNewsRecord>, "OpenWithNewsRecord"}, }; - // clang-format on - RegisterHandlers(functions); } INewsDataService::~INewsDataService() = default; +bool INewsDataService::TryOpen(std::string_view key, std::string_view user) { + opened_payload.clear(); + + if (auto found = NewsStorage::Instance().FindByNewsId(key, user)) { + opened_payload = std::move(found->payload); + return true; + } + + if (!user.empty()) { + if (auto found = NewsStorage::Instance().FindByNewsId(key)) { + opened_payload = std::move(found->payload); + return true; + } + } + + const auto list = NewsStorage::Instance().ListAll(); + if (!list.empty()) { + if (auto found = NewsStorage::Instance().FindByNewsId(ToStringView(list.front().news_id))) { + opened_payload = std::move(found->payload); + return true; + } + } + + return false; +} + +Result INewsDataService::Open(InBuffer name) { + EnsureBuiltinNewsLoaded(); + + const auto key = ToStringView({reinterpret_cast(name.data()), name.size()}); + + if (TryOpen(key, {})) { + R_SUCCEED(); + } + + R_RETURN(ResultUnknown); +} + +Result INewsDataService::OpenWithNewsRecordV1(NewsRecordV1 record) { + EnsureBuiltinNewsLoaded(); + + const auto key = ToStringView(record.news_id); + const auto user = ToStringView(record.user_id); + + if (TryOpen(key, user)) { + R_SUCCEED(); + } + + R_RETURN(ResultUnknown); +} + +Result INewsDataService::OpenWithNewsRecord(NewsRecord record) { + EnsureBuiltinNewsLoaded(); + + const auto key = ToStringView(record.news_id); + const auto user = ToStringView(record.user_id); + + if (TryOpen(key, user)) { + R_SUCCEED(); + } + + R_RETURN(ResultUnknown); +} + +Result INewsDataService::Read(Out out_size, s64 offset, + OutBuffer out_buffer) { + const auto off = static_cast(std::max(0, offset)); + + if (off >= opened_payload.size()) { + *out_size = 0; + R_SUCCEED(); + } + + const size_t len = (std::min)(out_buffer.size(), opened_payload.size() - off); + std::memcpy(out_buffer.data(), opened_payload.data() + off, len); + *out_size = len; + R_SUCCEED(); +} + +Result INewsDataService::GetSize(Out out_size) { + *out_size = static_cast(opened_payload.size()); + R_SUCCEED(); +} + } // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_data_service.h b/src/core/hle/service/bcat/news/news_data_service.h index 12082ada41..8f5e031701 100644 --- a/src/core/hle/service/bcat/news/news_data_service.h +++ b/src/core/hle/service/bcat/news/news_data_service.h @@ -1,10 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once +#include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" +#include "core/hle/service/bcat/news/news_storage.h" + namespace Core { class System; } @@ -15,6 +21,17 @@ class INewsDataService final : public ServiceFramework { public: explicit INewsDataService(Core::System& system_); ~INewsDataService() override; + +private: + bool TryOpen(std::string_view key, std::string_view user); + + Result Open(InBuffer name); + Result OpenWithNewsRecordV1(NewsRecordV1 record_buffer); + Result OpenWithNewsRecord(NewsRecord record_buffer); + Result Read(Out out_size, s64 offset, OutBuffer out_buffer); + Result GetSize(Out out_size); + + std::vector opened_payload; }; } // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_database_service.cpp b/src/core/hle/service/bcat/news/news_database_service.cpp index b94ef0636f..3580ceb0f7 100644 --- a/src/core/hle/service/bcat/news/news_database_service.cpp +++ b/src/core/hle/service/bcat/news/news_database_service.cpp @@ -1,52 +1,194 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/bcat/news/news_database_service.h" +#include "core/hle/service/bcat/news/builtin_news.h" +#include "core/hle/service/bcat/news/news_storage.h" #include "core/hle/service/cmif_serialization.h" +#include +#include + namespace Service::News { +namespace { + +std::string_view ToStringView(std::span buf) { + if (buf.empty()) return {}; + auto data = reinterpret_cast(buf.data()); + return {data, strnlen(data, buf.size())}; +} + +std::string_view ToStringView(std::span buf) { + if (buf.empty()) return {}; + return {buf.data(), strnlen(buf.data(), buf.size())}; +} + +bool UpdateField(NewsRecord& rec, std::string_view column, s32 value, bool additive) { + auto apply = [&](s32& field) { + field = additive ? field + value : value; + return true; + }; + + if (column == "read") return apply(rec.read); + if (column == "newly") return apply(rec.newly); + if (column == "displayed") return apply(rec.displayed); + if (column == "extra1" || column == "extra_1") return apply(rec.extra1); + if (column == "extra2" || column == "extra_2") return apply(rec.extra2); + + // Accept but ignore fields that don't exist in our struct + return column == "priority" || column == "decoration_type" || + column == "feedback" || column == "category"; +} + +} // namespace INewsDatabaseService::INewsDatabaseService(Core::System& system_) : ServiceFramework{system_, "INewsDatabaseService"} { - // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetListV1"}, + {0, D<&INewsDatabaseService::GetListV1>, "GetListV1"}, {1, D<&INewsDatabaseService::Count>, "Count"}, - {2, nullptr, "CountWithKey"}, - {3, nullptr, "UpdateIntegerValue"}, + {2, D<&INewsDatabaseService::CountWithKey>, "CountWithKey"}, + {3, D<&INewsDatabaseService::UpdateIntegerValue>, "UpdateIntegerValue"}, {4, D<&INewsDatabaseService::UpdateIntegerValueWithAddition>, "UpdateIntegerValueWithAddition"}, - {5, nullptr, "UpdateStringValue"}, + {5, D<&INewsDatabaseService::UpdateStringValue>, "UpdateStringValue"}, {1000, D<&INewsDatabaseService::GetList>, "GetList"}, }; - // clang-format on - RegisterHandlers(functions); } INewsDatabaseService::~INewsDatabaseService() = default; -Result INewsDatabaseService::Count(Out out_count, - InBuffer buffer_data) { - LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size()); - *out_count = 0; +Result INewsDatabaseService::Count(Out out_count, InBuffer where) { + EnsureBuiltinNewsLoaded(); + *out_count = static_cast(NewsStorage::Instance().ListAll().size()); R_SUCCEED(); } -Result INewsDatabaseService::UpdateIntegerValueWithAddition( - u32 value, InBuffer buffer_data_1, - InBuffer buffer_data_2) { - LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}", - value, buffer_data_1.size(), buffer_data_2.size()); +Result INewsDatabaseService::CountWithKey(Out out_count, + InBuffer key, + InBuffer where) { + EnsureBuiltinNewsLoaded(); + *out_count = static_cast(NewsStorage::Instance().ListAll().size()); R_SUCCEED(); } -Result INewsDatabaseService::GetList(Out out_count, u32 value, - OutBuffer out_buffer_data, - InBuffer buffer_data_1, - InBuffer buffer_data_2) { - LOG_WARNING(Service_BCAT, "(STUBBED) called, value={}, buffer_size_1={}, buffer_data_2={}", - value, buffer_data_1.size(), buffer_data_2.size()); - *out_count = 0; +Result INewsDatabaseService::UpdateIntegerValue(u32 value, + InBuffer key, + InBuffer where) { + const auto column = ToStringView(key); + for (const auto& rec : NewsStorage::Instance().ListAll()) { + NewsStorage::Instance().UpdateRecord( + ToStringView(rec.news_id), {}, + [&](NewsRecord& r) { UpdateField(r, column, static_cast(value), false); }); + } + R_SUCCEED(); +} + +Result INewsDatabaseService::UpdateIntegerValueWithAddition(u32 value, + InBuffer key, + InBuffer where) { + const auto column = ToStringView(key); + const auto where_str = ToStringView(where); + + // Extract news_id from where clause like "N_SWITCH(news_id,'LA00000000000123456',1,0)=1" + auto extract_news_id = [](std::string_view w) -> std::string { + auto pos = w.find("'LA"); + if (pos == std::string_view::npos) return {}; + pos++; // skip the ' + auto end = w.find("'", pos); + if (end == std::string_view::npos) return {}; + return std::string(w.substr(pos, end - pos)); + }; + + const auto news_id = extract_news_id(where_str); + + if (column == "read" && value > 0 && !news_id.empty()) { + NewsStorage::Instance().MarkAsRead(news_id); + } else if (!news_id.empty()) { + NewsStorage::Instance().UpdateRecord(news_id, {}, + [&](NewsRecord& r) { UpdateField(r, column, static_cast(value), true); }); + } + + R_SUCCEED(); +} + +Result INewsDatabaseService::UpdateStringValue(InBuffer key, + InBuffer value, + InBuffer where) { + LOG_WARNING(Service_BCAT, "(STUBBED) UpdateStringValue"); + R_SUCCEED(); +} + +Result INewsDatabaseService::GetListV1(Out out_count, + OutBuffer out_buffer, + InBuffer where, + InBuffer order, + s32 offset) { + EnsureBuiltinNewsLoaded(); + + auto record_size = sizeof(NewsRecordV1); + + if (out_buffer.size() < record_size) { + *out_count = 0; + R_SUCCEED(); + } + + std::memset(out_buffer.data(), 0, out_buffer.size()); + + const auto list = NewsStorage::Instance().ListAll(); + const size_t start = static_cast(std::max(0, offset)); + const size_t max_records = out_buffer.size() / record_size; + const size_t available = start < list.size() ? list.size() - start : 0; + const size_t count = (std::min)(max_records, available); + + for (size_t i = 0; i < count; ++i) { + const auto& src = list[start + i]; + NewsRecordV1 dst{}; + std::memcpy(dst.news_id.data(), src.news_id.data(), dst.news_id.size()); + std::memcpy(dst.user_id.data(), src.user_id.data(), dst.user_id.size()); + dst.received_time = src.received_time; + dst.read = src.read; + dst.newly = src.newly; + dst.displayed = src.displayed; + dst.extra1 = src.extra1; + std::memcpy(out_buffer.data() + i * record_size, &dst, record_size); + } + + *out_count = static_cast(count); + R_SUCCEED(); +} + +Result INewsDatabaseService::GetList(Out out_count, + OutBuffer out_buffer, + InBuffer where, + InBuffer order, + s32 offset) { + EnsureBuiltinNewsLoaded(); + NewsStorage::Instance().ResetOpenCounter(); + + auto record_size = sizeof(NewsRecord); + + if (out_buffer.size() < record_size) { + *out_count = 0; + R_SUCCEED(); + } + + std::memset(out_buffer.data(), 0, out_buffer.size()); + + const auto list = NewsStorage::Instance().ListAll(); + const size_t start = static_cast(std::max(0, offset)); + const size_t max_records = out_buffer.size() / record_size; + const size_t available = start < list.size() ? list.size() - start : 0; + const size_t count = (std::min)(max_records, available); + + if (count > 0) { + std::memcpy(out_buffer.data(), list.data() + start, count * record_size); + } + + *out_count = static_cast(count); R_SUCCEED(); } diff --git a/src/core/hle/service/bcat/news/news_database_service.h b/src/core/hle/service/bcat/news/news_database_service.h index 860b7074ca..143864e80e 100644 --- a/src/core/hle/service/bcat/news/news_database_service.h +++ b/src/core/hle/service/bcat/news/news_database_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,6 +9,8 @@ #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" +#include "core/hle/service/bcat/news/news_storage.h" + namespace Core { class System; } @@ -20,13 +25,30 @@ public: private: Result Count(Out out_count, InBuffer buffer_data); + Result CountWithKey(Out out_count, InBuffer key, + InBuffer where); + + Result UpdateIntegerValue(u32 value, InBuffer key, + InBuffer where); + Result UpdateIntegerValueWithAddition(u32 value, InBuffer buffer_data_1, InBuffer buffer_data_2); - Result GetList(Out out_count, u32 value, + Result UpdateStringValue(InBuffer key, + InBuffer value, + InBuffer where); + + Result GetListV1(Out out_count, + OutBuffer out_buffer_data, + InBuffer where_phrase, + InBuffer order_by_phrase, + s32 offset); + + Result GetList(Out out_count, OutBuffer out_buffer_data, - InBuffer buffer_data_1, - InBuffer buffer_data_2); + InBuffer where_phrase, + InBuffer order_by_phrase, + s32 offset); }; } // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_service.cpp b/src/core/hle/service/bcat/news/news_service.cpp index 09f7e67aff..aed1536bfe 100644 --- a/src/core/hle/service/bcat/news/news_service.cpp +++ b/src/core/hle/service/bcat/news/news_service.cpp @@ -5,32 +5,35 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/bcat/news/news_service.h" +#include "core/hle/service/bcat/news/news_storage.h" #include "core/hle/service/cmif_serialization.h" +#include + namespace Service::News { INewsService::INewsService(Core::System& system_) : ServiceFramework{system_, "INewsService"} { // clang-format off static const FunctionInfo functions[] = { {10100, D<&INewsService::PostLocalNews>, "PostLocalNews"}, - {20100, nullptr, "SetPassphrase"}, + {20100, D<&INewsService::SetPassphrase>, "SetPassphrase"}, {30100, D<&INewsService::GetSubscriptionStatus>, "GetSubscriptionStatus"}, - {30101, nullptr, "GetTopicList"}, //3.0.0+ - {30110, nullptr, "Unknown30110"}, //6.0.0+ + {30101, D<&INewsService::GetTopicList>, "GetTopicList"}, //3.0.0+ + {30110, D<&INewsService::GetTopicList>, "Unknown30110"}, //6.0.0+ (stub) {30200, D<&INewsService::IsSystemUpdateRequired>, "IsSystemUpdateRequired"}, - {30201, nullptr, "Unknown30201"}, //8.0.0+ - {30210, nullptr, "Unknown30210"}, //10.0.0+ + {30201, D<&INewsService::IsSystemUpdateRequired>, "Unknown30201"}, //8.0.0+ (stub) + {30210, D<&INewsService::IsSystemUpdateRequired>, "Unknown30210"}, //10.0.0+ (stub) {30300, nullptr, "RequestImmediateReception"}, - {30400, nullptr, "DecodeArchiveFile"}, //3.0.0-18.1.0 - {30500, nullptr, "Unknown30500"}, //8.0.0+ - {30900, nullptr, "Unknown30900"}, //1.0.0 - {30901, nullptr, "Unknown30901"}, //1.0.0 - {30902, nullptr, "Unknown30902"}, //1.0.0 + {30400, nullptr, "DecodeArchiveFile"}, //3.0.0-18.1.0 (stub) + {30500, nullptr, "Unknown30500"}, //8.0.0+ (stub) + {30900, nullptr, "Unknown30900"}, //1.0.0 (stub) + {30901, nullptr, "Unknown30901"}, //1.0.0 (stub) + {30902, nullptr, "Unknown30902"}, //1.0.0 (stub) {40100, nullptr, "SetSubscriptionStatus"}, {40101, D<&INewsService::RequestAutoSubscription>, "RequestAutoSubscription"}, //3.0.0+ - {40200, nullptr, "ClearStorage"}, - {40201, nullptr, "ClearSubscriptionStatusAll"}, - {90100, nullptr, "GetNewsDatabaseDump"}, + {40200, D<&INewsService::ClearStorage>, "ClearStorage"}, + {40201, D<&INewsService::ClearSubscriptionStatusAll>, "ClearSubscriptionStatusAll"}, + {90100, D<&INewsService::GetNewsDatabaseDump>, "GetNewsDatabaseDump"}, }; // clang-format on @@ -40,8 +43,7 @@ INewsService::INewsService(Core::System& system_) : ServiceFramework{system_, "I INewsService::~INewsService() = default; Result INewsService::PostLocalNews(InBuffer buffer_data) { - LOG_WARNING(Service_BCAT, "(STUBBED) called, buffer_size={}", buffer_data.size()); - + LOG_WARNING(Service_BCAT, "(STUBBED) PostLocalNews size={}", buffer_data.size()); R_SUCCEED(); } @@ -63,4 +65,44 @@ Result INewsService::RequestAutoSubscription(u64 value) { R_SUCCEED(); } +Result INewsService::SetPassphrase(InBuffer buffer_data) { + LOG_WARNING(Service_BCAT, "(STUBBED) SetPassphrase called size={}", buffer_data.size()); + R_SUCCEED(); +} + +Result INewsService::GetTopicList(Out out_count, OutBuffer out_topics, s32 filter) { + constexpr size_t TopicIdSize = 32; + constexpr auto EdenTopicId = "eden"; + + const size_t max_topics = out_topics.size() / TopicIdSize; + + if (max_topics == 0) { + *out_count = 0; + R_SUCCEED(); + } + + std::memset(out_topics.data(), 0, out_topics.size()); + + std::memcpy(out_topics.data(), EdenTopicId, std::strlen(EdenTopicId)); + *out_count = 1; + + R_SUCCEED(); +} + +Result INewsService::ClearStorage() { + LOG_WARNING(Service_BCAT, "(STUBBED) called"); + NewsStorage::Instance().Clear(); + R_SUCCEED(); +} + +Result INewsService::ClearSubscriptionStatusAll() { + LOG_WARNING(Service_BCAT, "(STUBBED) called"); + R_SUCCEED(); +} + +Result INewsService::GetNewsDatabaseDump() { + LOG_WARNING(Service_BCAT, "(STUBBED) called"); + R_SUCCEED(); +} + } // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_service.h b/src/core/hle/service/bcat/news/news_service.h index 245f08afc3..1a320df02f 100644 --- a/src/core/hle/service/bcat/news/news_service.h +++ b/src/core/hle/service/bcat/news/news_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -20,11 +23,21 @@ public: private: Result PostLocalNews(InBuffer buffer_data); + Result SetPassphrase(InBuffer buffer_data); + Result GetSubscriptionStatus(Out out_status, InBuffer buffer_data); + Result GetTopicList(Out out_count, OutBuffer out_topics, s32 filter); + Result IsSystemUpdateRequired(Out out_is_system_update_required); Result RequestAutoSubscription(u64 value); + + Result ClearStorage(); + + Result ClearSubscriptionStatusAll(); + + Result GetNewsDatabaseDump(); }; } // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_storage.cpp b/src/core/hle/service/bcat/news/news_storage.cpp new file mode 100644 index 0000000000..793c1fa94f --- /dev/null +++ b/src/core/hle/service/bcat/news/news_storage.cpp @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/bcat/news/news_storage.h" + +#include "common/fs/path_util.h" + +#include +#include +#include +#include +#include +#include + +namespace Service::News { +namespace { + +std::filesystem::path GetReadCachePath() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "news" / "news_read"; +} + +std::set LoadReadIds() { + std::set ids; + std::ifstream f(GetReadCachePath()); + std::string line; + while (std::getline(f, line)) { + if (!line.empty()) ids.insert(line); + } + return ids; +} + +void SaveReadIds(const std::set& ids) { + const auto path = GetReadCachePath(); + std::error_code ec; + std::filesystem::create_directories(path.parent_path(), ec); + std::ofstream f(path); + for (const auto& id : ids) { + f << id << '\n'; + } +} + +} // namespace + +NewsStorage& NewsStorage::Instance() { + static NewsStorage s; + return s; +} + +void NewsStorage::Clear() { + std::scoped_lock lk{mtx}; + items.clear(); +} + +void NewsStorage::CopyZ(std::span dst, std::string_view src) { + std::memset(dst.data(), 0, dst.size()); + std::memcpy(dst.data(), src.data(), (std::min)(dst.size() - 1, src.size())); +} + +std::string NewsStorage::MakeKey(std::string_view news_id, std::string_view user_id) { + return std::string(news_id) + "|" + std::string(user_id); +} + +s64 NewsStorage::Now() { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +StoredNews& NewsStorage::Upsert(std::string_view news_id, std::string_view user_id, + std::string_view topic_id, s64 time, std::vector payload) { + std::scoped_lock lk{mtx}; + + const auto key = MakeKey(news_id, user_id); + auto it = items.find(key); + const bool exists = it != items.end(); + + // implemented a little "read" file. All in cache. + const auto read_ids = LoadReadIds(); + const bool was_read = read_ids.contains(std::string(news_id)); + + NewsRecord rec{}; + CopyZ(rec.news_id, news_id); + CopyZ(rec.user_id, user_id); + // there is nx_notice and nx_promotion for tags pre-existing + CopyZ(rec.topic_id, topic_id.empty() ? "nx_notice" : topic_id); + + rec.received_time = exists ? it->second.record.received_time : (time ? time : Now()); + rec.read = was_read ? 1 : (exists ? it->second.record.read : 0); + rec.newly = was_read ? 0 : 1; + rec.displayed = exists ? it->second.record.displayed : 0; + rec.extra1 = exists ? it->second.record.extra1 : 0; + rec.extra2 = exists ? it->second.record.extra2 : 0; + + auto& entry = items[key]; + entry.record = rec; + entry.payload = std::move(payload); + return entry; +} + +StoredNews& NewsStorage::UpsertRaw(const GithubNewsMeta& meta, std::vector payload) { + return Upsert(meta.news_id, "", meta.topic_id, static_cast(meta.published_at), std::move(payload)); +} + +std::vector NewsStorage::ListAll() const { + std::scoped_lock lk{mtx}; + + std::vector out; + out.reserve(items.size()); + for (const auto& [_, v] : items) { + out.push_back(v.record); + } + + std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) { + return a.received_time > b.received_time; + }); + return out; +} + +std::optional NewsStorage::FindByNewsId(std::string_view news_id, + std::string_view user_id) const { + std::scoped_lock lk{mtx}; + + if (auto it = items.find(MakeKey(news_id, user_id)); it != items.end()) { + return it->second; + } + if (!user_id.empty()) { + if (auto it = items.find(MakeKey(news_id, "")); it != items.end()) { + return it->second; + } + } + return std::nullopt; +} + +bool NewsStorage::UpdateRecord(std::string_view news_id, std::string_view user_id, + const std::function& updater) { + std::scoped_lock lk{mtx}; + + if (auto it = items.find(MakeKey(news_id, user_id)); it != items.end()) { + updater(it->second.record); + return true; + } + return false; +} + +void NewsStorage::MarkAsRead(std::string_view news_id) { + std::scoped_lock lk{mtx}; + for (auto& [_, entry] : items) { + if (std::string_view(entry.record.news_id.data()) == news_id) { + entry.record.read = 1; + entry.record.newly = 0; + break; + } + } + auto ids = LoadReadIds(); + ids.insert(std::string(news_id)); + SaveReadIds(ids); +} + +size_t NewsStorage::GetAndIncrementOpenCounter() { + std::scoped_lock lk{mtx}; + return open_counter++; +} + +void NewsStorage::ResetOpenCounter() { + std::scoped_lock lk{mtx}; + open_counter = 0; +} + + +} // namespace Service::News diff --git a/src/core/hle/service/bcat/news/news_storage.h b/src/core/hle/service/bcat/news/news_storage.h new file mode 100644 index 0000000000..ad84e4080c --- /dev/null +++ b/src/core/hle/service/bcat/news/news_storage.h @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace Service::News { + +struct GithubNewsMeta { + std::string news_id; + std::string topic_id; + u64 published_at{}; + u64 pickup_limit{}; + u64 essential_pickup_limit{}; + u64 expire_at{}; + u32 priority{}; + u32 deletion_priority{100}; + u32 decoration_type{1}; + u32 opted_in{1}; + u32 essential_pickup_limit_flag{1}; + u32 category{}; + u32 language_mask{1}; +}; + +struct NewsRecordV1 { + std::array news_id{}; + std::array user_id{}; + s64 received_time{}; + s32 read{}; + s32 newly{}; + s32 displayed{}; + s32 extra1{}; +}; +static_assert(sizeof(NewsRecordV1) == 72); + +struct NewsRecord { + std::array news_id{}; + std::array user_id{}; + std::array topic_id{}; + s64 received_time{}; + std::array _pad1{}; + s32 read{}; + s32 newly{}; + s32 displayed{}; + std::array _pad2{}; + s32 extra1{}; + s32 extra2{}; +}; +static_assert(sizeof(NewsRecord) == 128); + +struct StoredNews { + NewsRecord record{}; + std::vector payload; +}; + +class NewsStorage { +public: + static NewsStorage& Instance(); + + void Clear(); + StoredNews& Upsert(std::string_view news_id, std::string_view user_id, + std::string_view topic_id, s64 time, std::vector payload); + StoredNews& UpsertRaw(const GithubNewsMeta& meta, std::vector payload); + + std::vector ListAll() const; + std::optional FindByNewsId(std::string_view news_id, + std::string_view user_id = {}) const; + bool UpdateRecord(std::string_view news_id, std::string_view user_id, + const std::function& updater); + void MarkAsRead(std::string_view news_id); + + size_t GetAndIncrementOpenCounter(); + void ResetOpenCounter(); + +private: + NewsStorage() = default; + + static std::string MakeKey(std::string_view news_id, std::string_view user_id); + static void CopyZ(std::span dst, std::string_view src); + static s64 Now(); + + mutable std::mutex mtx; + std::unordered_map items; + size_t open_counter{}; +}; + +} // namespace Service::News diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp index 6b7eab5efd..29db6594f4 100644 --- a/src/core/hle/service/erpt/erpt.cpp +++ b/src/core/hle/service/erpt/erpt.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -22,7 +25,7 @@ public: {2, nullptr, "SetInitialLaunchSettingsCompletionTime"}, {3, nullptr, "ClearInitialLaunchSettingsCompletionTime"}, {4, nullptr, "UpdatePowerOnTime"}, - {5, nullptr, "UpdateAwakeTime"}, + {5, D<&ErrorReportContext::UpdateAwakeTime>, "UpdateAwakeTime"}, {6, nullptr, "SubmitMultipleCategoryContext"}, {7, nullptr, "UpdateApplicationLaunchTime"}, {8, nullptr, "ClearApplicationLaunchTime"}, @@ -74,6 +77,14 @@ private: report_type, unknown, create_report_option_flag); R_SUCCEED(); } + + Result UpdateAwakeTime(InBuffer data_a, + InBuffer data_b, u32 flag_a, u32 flag_b) { + LOG_WARNING(Service_SET, + "(STUBBED) called, data_a_size={}, data_b_size={}, flag_a={}, flag_b={}", + data_a.size(), data_b.size(), flag_a, flag_b); + R_SUCCEED(); + } }; class ErrorReportSession final : public ServiceFramework { diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp index 352b8f77b0..0638111ae9 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -163,11 +163,42 @@ Result IFileSystem::GetFileTimeStampRaw( Result IFileSystem::GetFileSystemAttribute(Out out_attribute) { LOG_WARNING(Service_FS, "(STUBBED) called"); + constexpr s32 kEntryNameLengthMax = 0x80; + constexpr s32 kPathLengthMax = 0x300; + FileSys::FileSystemAttribute savedata_attribute{}; + savedata_attribute.dir_entry_name_length_max_defined = true; savedata_attribute.file_entry_name_length_max_defined = true; - savedata_attribute.dir_entry_name_length_max = 0x40; - savedata_attribute.file_entry_name_length_max = 0x40; + savedata_attribute.dir_path_name_length_max_defined = true; + savedata_attribute.file_path_name_length_max_defined = true; + + savedata_attribute.utf16_create_dir_path_len_max_defined = true; + savedata_attribute.utf16_delete_dir_path_len_max_defined = true; + savedata_attribute.utf16_rename_src_dir_path_len_max_defined = true; + savedata_attribute.utf16_rename_dest_dir_path_len_max_defined = true; + savedata_attribute.utf16_open_dir_path_len_max_defined = true; + + savedata_attribute.utf16_dir_entry_name_length_max_defined = true; + savedata_attribute.utf16_file_entry_name_length_max_defined = true; + savedata_attribute.utf16_dir_path_name_length_max_defined = true; + savedata_attribute.utf16_file_path_name_length_max_defined = true; + + savedata_attribute.dir_entry_name_length_max = kEntryNameLengthMax; + savedata_attribute.file_entry_name_length_max = kEntryNameLengthMax; + savedata_attribute.dir_path_name_length_max = kPathLengthMax; + savedata_attribute.file_path_name_length_max = kPathLengthMax; + + savedata_attribute.utf16_create_dir_path_length_max = kPathLengthMax; + savedata_attribute.utf16_delete_dir_path_length_max = kPathLengthMax; + savedata_attribute.utf16_rename_src_dir_path_length_max = kPathLengthMax; + savedata_attribute.utf16_rename_dest_dir_path_length_max = kPathLengthMax; + savedata_attribute.utf16_open_dir_path_length_max = kPathLengthMax; + + savedata_attribute.utf16_dir_entry_name_length_max = kEntryNameLengthMax; + savedata_attribute.utf16_file_entry_name_length_max = kEntryNameLengthMax; + savedata_attribute.utf16_dir_path_name_length_max = kPathLengthMax; + savedata_attribute.utf16_file_path_name_length_max = kPathLengthMax; *out_attribute = savedata_attribute; R_SUCCEED(); diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index f65e70a969..563c796c15 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -53,6 +53,7 @@ public: {20102, nullptr, "GetFriendDetailedInfo"}, {20103, nullptr, "SyncFriendList"}, {20104, &IFriendService::RequestSyncFriendList, "RequestSyncFriendList"}, + {20105, &IFriendService::GetFriendListForViewer, "GetFriendListForViewer"}, {20110, nullptr, "LoadFriendSetting"}, {20200, &IFriendService::GetReceivedFriendRequestCount, "GetReceivedFriendRequestCount"}, {20201, nullptr, "GetFriendRequestList"}, @@ -65,11 +66,13 @@ public: {20401, nullptr, "SyncBlockedUserList"}, {20500, nullptr, "GetProfileExtraList"}, {20501, nullptr, "GetRelationship"}, - {20600, &IFriendService::GetUserPresenceView, "GetUserPresenceView"}, + {20600, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV1"}, + {20601, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV2"}, {20700, nullptr, "GetPlayHistoryList"}, {20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"}, - {20800, &IFriendService::LoadUserSetting, "LoadUserSetting"}, + {20800, &IFriendService::LoadUserSetting, "LoadUserSettingV1"}, {20801, nullptr, "SyncUserSetting"}, + {20802, &IFriendService::LoadUserSetting, "LoadUserSettingV2"}, {20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"}, {21000, nullptr, "GetExternalApplicationCatalog"}, {22000, nullptr, "GetReceivedFriendInvitationList"}, @@ -270,6 +273,14 @@ private: rb.Push(ResultSuccess); } + void GetFriendListForViewer(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } + void GetReceivedFriendRequestCount(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; [[maybe_unused]] const auto uuid = rp.PopRaw(); diff --git a/src/core/hle/service/ldn/user_local_communication_service.cpp b/src/core/hle/service/ldn/user_local_communication_service.cpp index d6a67ed75b..1e984a9782 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.cpp +++ b/src/core/hle/service/ldn/user_local_communication_service.cpp @@ -38,6 +38,7 @@ IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& sys {102, D<&IUserLocalCommunicationService::Scan>, "Scan"}, {103, D<&IUserLocalCommunicationService::ScanPrivate>, "ScanPrivate"}, {104, D<&IUserLocalCommunicationService::SetWirelessControllerRestriction>, "SetWirelessControllerRestriction"}, + { 106, D<&IUserLocalCommunicationService::SetProtocol>, "SetProtocol" }, {200, D<&IUserLocalCommunicationService::OpenAccessPoint>, "OpenAccessPoint"}, {201, D<&IUserLocalCommunicationService::CloseAccessPoint>, "CloseAccessPoint"}, {202, D<&IUserLocalCommunicationService::CreateNetwork>, "CreateNetwork"}, @@ -188,6 +189,11 @@ Result IUserLocalCommunicationService::ScanPrivate( R_RETURN(lan_discovery.Scan(out_network_info, *network_count, scan_filter)); } +Result IUserLocalCommunicationService::SetProtocol(u32 protocol) { + LOG_WARNING(Service_LDN, "(STUBBED) called, protocol={}", protocol); + R_SUCCEED(); +} + Result IUserLocalCommunicationService::SetWirelessControllerRestriction( WirelessControllerRestriction wireless_restriction) { LOG_WARNING(Service_LDN, "(STUBBED) called"); diff --git a/src/core/hle/service/ldn/user_local_communication_service.h b/src/core/hle/service/ldn/user_local_communication_service.h index 55320fbc91..1e725ed531 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.h +++ b/src/core/hle/service/ldn/user_local_communication_service.h @@ -49,6 +49,8 @@ private: Result ScanPrivate(Out network_count, WifiChannel channel, const ScanFilter& scan_filter, OutArray out_network_info); + Result SetProtocol(u32 protocol); + Result SetWirelessControllerRestriction(WirelessControllerRestriction wireless_restriction); Result OpenAccessPoint(); diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp index 45d452cee1..b0f74bef06 100644 --- a/src/core/hle/service/npns/npns.cpp +++ b/src/core/hle/service/npns/npns.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -18,7 +18,8 @@ namespace Service::NPNS { class INpnsSystem final : public ServiceFramework { public: explicit INpnsSystem(Core::System& system_) - : ServiceFramework{system_, "npns:s"}, service_context{system, "npns:s"} { + : ServiceFramework{system_, "npns:s"}, service_context{system, "npns:s"}, + get_receive_event{service_context}, get_request_change_state_cancel_event{service_context} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -91,16 +92,9 @@ public: // clang-format on RegisterHandlers(functions); - - get_receive_event = service_context.CreateEvent("npns:s:GetReceiveEvent"); - get_request_change_state_cancel_event = - service_context.CreateEvent("npns:s:GetRequestChangeStateCancelEvent"); } - ~INpnsSystem() override { - service_context.CloseEvent(get_receive_event); - service_context.CloseEvent(get_request_change_state_cancel_event); - } + ~INpnsSystem() override = default; private: Result ListenTo(u32 program_id) { @@ -111,7 +105,7 @@ private: Result GetReceiveEvent(OutCopyHandle out_event) { LOG_WARNING(Service_NPNS, "(STUBBED) called"); - *out_event = &get_receive_event->GetReadableEvent(); + *out_event = get_receive_event.GetHandle(); R_SUCCEED(); } @@ -141,20 +135,20 @@ private: // TODO (jarrodnorwell) - *out_event = &get_request_change_state_cancel_event->GetReadableEvent(); + *out_event = get_request_change_state_cancel_event.GetHandle(); R_SUCCEED(); } KernelHelpers::ServiceContext service_context; - Kernel::KEvent* get_receive_event; - Kernel::KEvent* get_request_change_state_cancel_event; + Event get_receive_event; + Event get_request_change_state_cancel_event; }; class INpnsUser final : public ServiceFramework { public: explicit INpnsUser(Core::System& system_) - : ServiceFramework{system_, "npns:u"}, service_context{system, "npns:u"} { + : ServiceFramework{system_, "npns:u"}, service_context{system, "npns:u"}, get_receive_event{service_context} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -182,12 +176,6 @@ public: // clang-format on RegisterHandlers(functions); - - get_receive_event = service_context.CreateEvent("npns:u:GetReceiveEvent"); - } - - ~INpnsUser() override { - service_context.CloseEvent(get_receive_event); } private: @@ -203,12 +191,12 @@ private: Result GetReceiveEvent(OutCopyHandle out_event) { LOG_DEBUG(Service_NPNS, "called"); - *out_event = &get_receive_event->GetReadableEvent(); + *out_event = get_receive_event.GetHandle(); R_SUCCEED(); } KernelHelpers::ServiceContext service_context; - Kernel::KEvent* get_receive_event; + Event get_receive_event; }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 77481a0d98..bed5057f37 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -16,6 +16,10 @@ #include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/file_sys/patch_manager.h" #include "frontend_common/firmware_manager.h" +#include "core/launch_timestamp_cache.h" + +#include +#include namespace Service::NS { @@ -132,7 +136,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {404, nullptr, "InvalidateApplicationControlCache"}, {405, nullptr, "ListApplicationControlCacheEntryInfo"}, {406, nullptr, "GetApplicationControlProperty"}, - {407, nullptr, "ListApplicationTitle"}, + {407, &IApplicationManagerInterface::ListApplicationTitle, "ListApplicationTitle"}, {408, nullptr, "ListApplicationIcon"}, {411, nullptr, "Unknown411"}, //19.0.0+ {412, nullptr, "Unknown412"}, //19.0.0+ @@ -357,7 +361,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {2517, nullptr, "CreateApplicationInstance"}, {2518, nullptr, "UpdateQualificationForDebug"}, {2519, nullptr, "IsQualificationTransitionSupported"}, - {2520, nullptr, "IsQualificationTransitionSupportedByProcessId"}, + {2520, D<&IApplicationManagerInterface::IsQualificationTransitionSupportedByProcessId>, "IsQualificationTransitionSupportedByProcessId"}, {2521, nullptr, "GetRightsUserChangedEvent"}, {2522, nullptr, "IsRomRedirectionAvailable"}, {2523, nullptr, "GetProgramId"}, //17.0.0+ @@ -560,33 +564,42 @@ Result IApplicationManagerInterface::ListApplicationRecord( s32 offset) { const auto limit = out_records.size(); - LOG_WARNING(Service_NS, "(STUBBED) called"); + LOG_DEBUG(Service_NS, "called"); const auto& cache = system.GetContentProviderUnion(); const auto installed_games = cache.ListEntriesFilterOrigin( std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); - size_t i = 0; - u8 ii = 24; + std::vector records; + records.reserve(installed_games.size()); for (const auto& [slot, game] : installed_games) { - if (i >= limit) { - break; - } - if (game.title_id == 0 || game.title_id < 0x0100000000001FFFull) { - continue; - } - if (offset > 0) { - offset--; - continue; - } + if (game.title_id == 0 || game.title_id < 0x0100000000001FFFull) { + continue; + } + if ((game.title_id & 0xFFF) != 0) { + continue; // skip sub-programs (e.g., 001) + } - ApplicationRecord record{}; - record.application_id = game.title_id; - record.type = ApplicationRecordType::Installed; - record.unknown = 0; // 2 = needs update - record.unknown2 = ii++; + ApplicationRecord record{}; + record.application_id = game.title_id; + record.last_event = ApplicationEvent::Installed; + record.attributes = 0; + record.last_updated = Core::LaunchTimestampCache::GetLaunchTimestamp(game.title_id); - out_records[i++] = record; + records.push_back(record); + } + + std::sort(records.begin(), records.end(), [](const ApplicationRecord& lhs, const ApplicationRecord& rhs) { + if (lhs.last_updated == rhs.last_updated) { + return lhs.application_id < rhs.application_id; + } + return lhs.last_updated > rhs.last_updated; + }); + + size_t i = 0; + const size_t start = static_cast(std::max(0, offset)); + for (size_t idx = start; idx < records.size() && i < limit; ++idx) { + out_records[i++] = records[idx]; } *out_count = static_cast(i); @@ -756,6 +769,13 @@ Result IApplicationManagerInterface::ResumeAll() { R_SUCCEED(); } +Result IApplicationManagerInterface::IsQualificationTransitionSupportedByProcessId( + Out out_is_supported, u64 process_id) { + LOG_WARNING(Service_NS, "(STUBBED) called, process_id={}", process_id); + *out_is_supported = true; + R_SUCCEED(); +} + Result IApplicationManagerInterface::GetStorageSize(Out out_total_space_size, Out out_free_space_size, FileSys::StorageId storage_id) { @@ -819,4 +839,9 @@ Result IApplicationManagerInterface::Unknown4053() { R_SUCCEED(); } +void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index e7bab6dca1..ffb744d3fa 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -53,6 +53,8 @@ public: Result GetFreeSpaceSize(Out out_free_space_size, FileSys::StorageId storage_id); Result GetGameCardUpdateDetectionEvent(OutCopyHandle out_event); Result ResumeAll(); + Result IsQualificationTransitionSupportedByProcessId(Out out_is_supported, + u64 process_id); Result GetStorageSize(Out out_total_space_size, Out out_free_space_size, FileSys::StorageId storage_id); Result TouchApplication(u64 application_id); @@ -71,6 +73,8 @@ public: Result RequestDownloadApplicationControlDataInBackground(u64 control_source, u64 application_id); + void ListApplicationTitle(HLERequestContext& ctx); + private: KernelHelpers::ServiceContext service_context; Event record_update_system_event; diff --git a/src/core/hle/service/ns/content_management_interface.cpp b/src/core/hle/service/ns/content_management_interface.cpp index 09c15a3e0f..39b9cb6f70 100644 --- a/src/core/hle/service/ns/content_management_interface.cpp +++ b/src/core/hle/service/ns/content_management_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -21,6 +21,7 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) {43, D<&IContentManagementInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"}, {47, D<&IContentManagementInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"}, {48, D<&IContentManagementInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"}, + {71, D<&IContentManagementInterface::GetUnknown71>, "Unknown71"}, {600, nullptr, "CountApplicationContentMeta"}, {601, nullptr, "ListApplicationContentMetaStatus"}, {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, @@ -73,4 +74,12 @@ Result IContentManagementInterface::GetFreeSpaceSize(Out out_free_space_siz R_SUCCEED(); } +Result IContentManagementInterface::GetUnknown71(Out out_value_a, Out out_value_b, + u8 flag) { + LOG_INFO(Service_NS, "(STUBBED) called, flag={:02X}", flag); + *out_value_a = 0; + *out_value_b = 0; + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/content_management_interface.h b/src/core/hle/service/ns/content_management_interface.h index 2894628e52..df0c51a184 100644 --- a/src/core/hle/service/ns/content_management_interface.h +++ b/src/core/hle/service/ns/content_management_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,6 +23,7 @@ public: Result CheckSdCardMountStatus(); Result GetTotalSpaceSize(Out out_total_space_size, FileSys::StorageId storage_id); Result GetFreeSpaceSize(Out out_free_space_size, FileSys::StorageId storage_id); + Result GetUnknown71(Out out_value_a, Out out_value_b, u8 flag); }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/dynamic_rights_interface.cpp b/src/core/hle/service/ns/dynamic_rights_interface.cpp index 22c62293a2..0301085670 100644 --- a/src/core/hle/service/ns/dynamic_rights_interface.cpp +++ b/src/core/hle/service/ns/dynamic_rights_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -68,9 +68,9 @@ Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) { } Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications( - Out out_status, u64 rights_handle) { - LOG_WARNING(Service_NS, "(STUBBED) called, rights_handle={:#x}", rights_handle); - *out_status = 0; + Out out_is_restricted) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + *out_is_restricted = 0; R_SUCCEED(); } diff --git a/src/core/hle/service/ns/dynamic_rights_interface.h b/src/core/hle/service/ns/dynamic_rights_interface.h index e9429ea915..c3270aee05 100644 --- a/src/core/hle/service/ns/dynamic_rights_interface.h +++ b/src/core/hle/service/ns/dynamic_rights_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -20,8 +20,7 @@ private: Result NotifyApplicationRightsCheckStart(); Result GetRunningApplicationStatus(Out out_status, u64 rights_handle); Result VerifyActivatedRightsOwners(u64 rights_handle); - Result HasAccountRestrictedRightsInRunningApplications(Out out_status, - u64 rights_handle); + Result HasAccountRestrictedRightsInRunningApplications(Out out_is_restricted); }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h index 247f7063af..c3b4856e7f 100644 --- a/src/core/hle/service/ns/ns_types.h +++ b/src/core/hle/service/ns/ns_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -12,7 +12,7 @@ namespace Service::NS { -enum class ApplicationRecordType : u8 { +enum class ApplicationEvent : u8 { Installing = 2, Installed = 3, GameCardNotInserted = 5, @@ -34,11 +34,10 @@ enum class BackgroundNetworkUpdateState : u8 { struct ApplicationRecord { u64 application_id; - ApplicationRecordType type; - u8 unknown; + ApplicationEvent last_event; + u8 attributes; INSERT_PADDING_BYTES_NOINIT(0x6); - u8 unknown2; - INSERT_PADDING_BYTES_NOINIT(0x7); + s64 last_updated; }; static_assert(sizeof(ApplicationRecord) == 0x18, "ApplicationRecord has incorrect size."); @@ -48,9 +47,9 @@ struct ApplicationDownloadState { u64 total_size; u32 unk_x10; u8 state; - u8 unk_x19; - std::array unk_x1a; - u64 unk_x20; + u8 unk_x15; + std::array unk_x16; + u64 unk_x18; }; static_assert(sizeof(ApplicationDownloadState) == 0x20, "ApplicationDownloadState has incorrect size."); diff --git a/src/core/hle/service/ns/platform_service_manager.cpp b/src/core/hle/service/ns/platform_service_manager.cpp index 293c014eae..ec9f64945d 100644 --- a/src/core/hle/service/ns/platform_service_manager.cpp +++ b/src/core/hle/service/ns/platform_service_manager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/common_types.h" @@ -40,96 +41,51 @@ constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; constexpr FontRegion EMPTY_REGION{0, 0}; -static void DecryptSharedFont(const std::vector& input, Kernel::PhysicalMemory& output, - std::size_t& offset) { - ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, - "Shared fonts exceeds 17mb!"); - ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); - +static void DecryptSharedFont(const std::span input, std::span output, std::size_t& offset) { + ASSERT(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE && "Shared fonts exceeds 17mb!"); + ASSERT(input[0] == EXPECTED_MAGIC && "Failed to derive key, unexpected magic number"); const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor std::vector transformed_font(input.size()); // TODO(ogniK): Figure out a better way to do this - std::transform(input.begin(), input.end(), transformed_font.begin(), - [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + std::transform(input.begin(), input.end(), transformed_font.begin(), [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size - std::memcpy(output.data() + offset, transformed_font.data(), - transformed_font.size() * sizeof(u32)); + std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32)); offset += transformed_font.size() * sizeof(u32); } void DecryptSharedFontToTTF(const std::vector& input, std::vector& output) { ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); - if (input.size() < 2) { LOG_ERROR(Service_NS, "Input font is empty"); return; } - const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor std::vector transformed_font(input.size()); // TODO(ogniK): Figure out a better way to do this - std::transform(input.begin(), input.end(), transformed_font.begin(), - [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); - std::memcpy(output.data(), transformed_font.data() + 2, - (transformed_font.size() - 2) * sizeof(u32)); + std::transform(input.begin(), input.end(), transformed_font.begin(), [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + std::memcpy(output.data(), transformed_font.data() + 2, (transformed_font.size() - 2) * sizeof(u32)); } -void EncryptSharedFont(const std::vector& input, std::vector& output, - std::size_t& offset) { - ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, - "Shared fonts exceeds 17mb!"); - +void EncryptSharedFont(const std::vector& input, std::vector& output, std::size_t& offset) { + ASSERT(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE && "Shared fonts exceeds 17mb!"); const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC); std::vector transformed_font(input.size() + 2); transformed_font[0] = Common::swap32(EXPECTED_MAGIC); transformed_font[1] = Common::swap32(static_cast(input.size() * sizeof(u32))) ^ key; - std::transform(input.begin(), input.end(), transformed_font.begin() + 2, - [key](u32 in) { return in ^ key; }); - std::memcpy(output.data() + offset, transformed_font.data(), - transformed_font.size() * sizeof(u32)); + std::transform(input.begin(), input.end(), transformed_font.begin() + 2, [key](u32 in) { return in ^ key; }); + std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32)); offset += transformed_font.size() * sizeof(u32); } -// Helper function to make BuildSharedFontsRawRegions a bit nicer -static u32 GetU32Swapped(const u8* data) { - u32 value; - std::memcpy(&value, data, sizeof(value)); - return Common::swap32(value); -} - struct IPlatformServiceManager::Impl { const FontRegion& GetSharedFontRegion(std::size_t index) const { - if (index >= shared_font_regions.size() || shared_font_regions.empty()) { - // No font fallback - return EMPTY_REGION; - } - return shared_font_regions.at(index); + return index < shared_font_regions.size() ? shared_font_regions[index] : EMPTY_REGION; } - - void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) { - // As we can derive the xor key we can just populate the offsets - // based on the shared memory dump - unsigned cur_offset = 0; - - for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) { - // Out of shared fonts/invalid font - if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) { - break; - } - - // Derive key within inverse xor - const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC; - const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; - shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE}); - cur_offset += SIZE + 8; - } - } - - /// Backing memory for the shared font data - std::shared_ptr shared_font; - // Automatically populated based on shared_fonts dump or system archives. - std::vector shared_font_regions; + // 6 builtin fonts + extra 2 for whatever may come after + boost::container::static_vector shared_font_regions; + /// Backing memory for the shared font data + std::array shared_font; }; IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_) @@ -162,8 +118,6 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch const auto* nand = fsc.GetSystemNANDContents(); std::size_t offset = 0; // Rebuild shared fonts from data ncas or synthesize - - impl->shared_font = std::make_shared(SHARED_FONT_MEM_SIZE); for (auto& font : SHARED_FONTS) { FileSys::VirtualFile romfs; const auto nca = @@ -197,9 +151,8 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), Common::swap32); // Font offset and size do not account for the header - const FontRegion region{static_cast(offset + 8), - static_cast((font_data_u32.size() * sizeof(u32)) - 8)}; - DecryptSharedFont(font_data_u32, *impl->shared_font, offset); + const FontRegion region{u32(offset + 8), u32((font_data_u32.size() * sizeof(u32)) - 8)}; + DecryptSharedFont(font_data_u32, impl->shared_font, offset); impl->shared_font_regions.push_back(region); } } @@ -231,14 +184,12 @@ Result IPlatformServiceManager::GetSharedMemoryAddressOffset(Out out_shared R_SUCCEED(); } -Result IPlatformServiceManager::GetSharedMemoryNativeHandle( - OutCopyHandle out_shared_memory_native_handle) { +Result IPlatformServiceManager::GetSharedMemoryNativeHandle(OutCopyHandle out_shared_memory_native_handle) { // Map backing memory for the font data LOG_DEBUG(Service_NS, "called"); // Create shared font memory object - std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(), - impl->shared_font->size()); + std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font.data(), impl->shared_font.size()); // FIXME: this shouldn't belong to the kernel *out_shared_memory_native_handle = &kernel.GetFontSharedMem(); diff --git a/src/core/hle/service/ns/query_service.cpp b/src/core/hle/service/ns/query_service.cpp index a4632cb6c8..d4d5834f3f 100644 --- a/src/core/hle/service/ns/query_service.cpp +++ b/src/core/hle/service/ns/query_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -9,10 +9,13 @@ #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/query_service.h" #include "core/hle/service/service.h" +#include "core/launch_timestamp_cache.h" +#include "frontend_common/play_time_manager.h" namespace Service::NS { -IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, "pdm:qry"} { +IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, "pdm:qry"}, + play_time_manager{std::make_unique()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "QueryAppletEvent"}, @@ -33,8 +36,8 @@ IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, {15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"}, {16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"}, {17, D<&IQueryService::QueryLastPlayTime>, "QueryLastPlayTime"}, - {18, nullptr, "QueryApplicationPlayStatisticsForSystem"}, - {19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, + {18, D<&IQueryService::QueryApplicationPlayStatisticsForSystem>, "QueryApplicationPlayStatisticsForSystem"}, + {19, D<&IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem>, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, }; // clang-format on @@ -65,4 +68,32 @@ Result IQueryService::QueryLastPlayTime( R_SUCCEED(); } +Result IQueryService::QueryApplicationPlayStatisticsForSystem( + Out out_entries, u8 flag, + OutArray out_stats, + InArray application_ids) { + const size_t count = std::min(out_stats.size(), application_ids.size()); + s32 written = 0; + for (size_t i = 0; i < count; ++i) { + const u64 app_id = application_ids[i]; + ApplicationPlayStatistics stats{}; + stats.application_id = app_id; + stats.play_time_ns = play_time_manager->GetPlayTime(app_id) * 1'000'000'000ULL; + stats.launch_count = Core::LaunchTimestampCache::GetLaunchCount(app_id); + out_stats[i] = stats; + ++written; + } + *out_entries = written; + LOG_DEBUG(Service_NS, "called, entries={} flag={}", written, flag); + R_SUCCEED(); +} + +Result IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem( + Out out_entries, u8 flag, Common::UUID user_id, + OutArray out_stats, + InArray application_ids) { + // well we don't do per-user tracking :> + return QueryApplicationPlayStatisticsForSystem(out_entries, flag, out_stats, application_ids); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/query_service.h b/src/core/hle/service/ns/query_service.h index ba1cddd4ca..f5c0ae1058 100644 --- a/src/core/hle/service/ns/query_service.h +++ b/src/core/hle/service/ns/query_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,10 +7,17 @@ #pragma once #include "common/uuid.h" +#include "core/hle/service/am/am_types.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/service.h" +#include + +namespace PlayTime { +class PlayTimeManager; +} + namespace Service::NS { struct PlayStatistics { @@ -28,6 +35,13 @@ static_assert(sizeof(PlayStatistics) == 0x28, "PlayStatistics is an invalid size struct LastPlayTime {}; +struct ApplicationPlayStatistics { + u64 application_id{}; + u64 play_time_ns{}; + u64 launch_count{}; +}; +static_assert(sizeof(ApplicationPlayStatistics) == 0x18, "ApplicationPlayStatistics is an invalid size"); + class IQueryService final : public ServiceFramework { public: explicit IQueryService(Core::System& system_); @@ -39,6 +53,16 @@ private: Result QueryLastPlayTime(Out out_entries, u8 unknown, OutArray out_last_play_times, InArray application_ids); + Result QueryApplicationPlayStatisticsForSystem( + Out out_entries, u8 flag, + OutArray out_stats, + InArray application_ids); + Result QueryApplicationPlayStatisticsByUserAccountIdForSystem( + Out out_entries, u8 flag, Common::UUID user_id, + OutArray out_stats, + InArray application_ids); + + std::unique_ptr play_time_manager; }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp index 0115b83fd0..03f0a17865 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -6,20 +6,24 @@ #include #include +#include #include #include #include +#include #include "common/settings.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs/vfs.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/set/settings_server.h" +#include "core/hle/service/kernel_helpers.h" namespace Service::NS { @@ -66,6 +70,70 @@ void SanitizeJPEGImageSize(std::vector& image) { } // namespace + +// IAsyncValue implementation for ListApplicationTitle +// https://switchbrew.org/wiki/NS_services#ListApplicationTitle +class IAsyncValueForListApplicationTitle final : public ServiceFramework { +public: + explicit IAsyncValueForListApplicationTitle(Core::System& system_, s32 offset, s32 size) + : ServiceFramework{system_, "IAsyncValue"}, service_context{system_, "IAsyncValue"}, + data_offset{offset}, data_size{size} { + static const FunctionInfo functions[] = { + {0, &IAsyncValueForListApplicationTitle::GetSize, "GetSize"}, + {1, &IAsyncValueForListApplicationTitle::Get, "Get"}, + {2, &IAsyncValueForListApplicationTitle::Cancel, "Cancel"}, + {3, &IAsyncValueForListApplicationTitle::GetErrorContext, "GetErrorContext"}, + }; + RegisterHandlers(functions); + + completion_event = service_context.CreateEvent("IAsyncValue:Completion"); + completion_event->GetReadableEvent().Signal(); + } + + ~IAsyncValueForListApplicationTitle() override { + service_context.CloseEvent(completion_event); + } + + Kernel::KReadableEvent& ReadableEvent() const { + return completion_event->GetReadableEvent(); + } + +private: + void GetSize(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(data_size); + } + + void Get(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + std::vector buffer(sizeof(s32)); + std::memcpy(buffer.data(), &data_offset, sizeof(s32)); + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Cancel(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetErrorContext(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* completion_event{}; + s32 data_offset; + s32 data_size; +}; + IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface( Core::System& system_) : ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} { @@ -76,8 +144,9 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, - {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlDataWithoutIcon"}, - {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlDataWithoutIcon3"}, + {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlData"}, + {13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"}, + {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, }; // clang-format on @@ -240,6 +309,60 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2( R_SUCCEED(); } + +void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) { + /* + IPC::RequestParser rp{ctx}; + auto control_source = rp.PopRaw(); + rp.Skip(7, false); + auto transfer_memory_size = rp.Pop(); + */ + + const auto app_ids_buffer = ctx.ReadBuffer(); + const size_t app_count = app_ids_buffer.size() / sizeof(u64); + + std::vector application_ids(app_count); + if (app_count > 0) { + std::memcpy(application_ids.data(), app_ids_buffer.data(), app_count * sizeof(u64)); + } + + auto t_mem_obj = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); + auto* t_mem = t_mem_obj.GetPointerUnsafe(); + + constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry); + const size_t total_data_size = app_count * title_entry_size; + + constexpr s32 data_offset = 0; + + if (t_mem != nullptr && app_count > 0) { + auto& memory = system.ApplicationMemory(); + const auto t_mem_address = t_mem->GetSourceAddress(); + + for (size_t i = 0; i < app_count; ++i) { + const u64 app_id = application_ids[i]; + const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + FileSys::LanguageEntry entry{}; + if (control.first != nullptr) { + entry = control.first->GetLanguageEntry(); + } + + const size_t offset = i * title_entry_size; + memory.WriteBlock(t_mem_address + offset, &entry, title_entry_size); + } + } + + auto async_value = std::make_shared( + system, data_offset, static_cast(total_data_size)); + + IPC::ResponseBuilder rb{ctx, 2, 1, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(async_value->ReadableEvent()); + rb.PushIpcInterface(std::move(async_value)); +} + Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( OutBuffer out_buffer, Out out_flags_a, Out out_flags_b, Out out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) { diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index 4da45736b8..441408b651 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -34,6 +34,7 @@ public: u8 flag1, u8 flag2, u64 application_id); + void ListApplicationTitle(HLERequestContext& ctx); Result GetApplicationControlData3( OutBuffer out_buffer, Out out_flags_a, diff --git a/src/core/hle/service/nvdrv/devices/ioctl_serialization.h b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h index b12bcd1384..c3baa3a75b 100644 --- a/src/core/hle/service/nvdrv/devices/ioctl_serialization.h +++ b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -41,7 +44,7 @@ NvResult WrapGeneric(F&& callable, std::span input, std::span 0) { std::memcpy(&fixed, input.data(), var_offset); } @@ -74,14 +77,14 @@ NvResult WrapGeneric(F&& callable, std::span input, std::span 0) { - std::memcpy(output.data(), &fixed, std::min(output.size(), sizeof(FixedArg))); + std::memcpy(output.data(), &fixed, (std::min)(output.size(), sizeof(FixedArg))); } } if constexpr (HasVarArg) { if (num_var_args > 0 && output.size() > var_offset) { const size_t max_var_size = output.size() - var_offset; - std::memcpy(output.data() + var_offset, var_args.data(), std::min(max_var_size, num_var_args * sizeof(VarArg))); + std::memcpy(output.data() + var_offset, var_args.data(), (std::min)(max_var_size, num_var_args * sizeof(VarArg))); } } diff --git a/src/core/hle/service/olsc/daemon_controller.cpp b/src/core/hle/service/olsc/daemon_controller.cpp index 45f0f75b94..c449c6ff31 100644 --- a/src/core/hle/service/olsc/daemon_controller.cpp +++ b/src/core/hle/service/olsc/daemon_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -21,7 +21,7 @@ IDaemonController::IDaemonController(Core::System& system_) {5, D<&IDaemonController::GetGlobalAutoDownloadSetting>, "GetGlobalAutoDownloadSetting"}, // 11.0.0+ {6, D<&IDaemonController::SetGlobalAutoDownloadSetting>, "SetGlobalAutoDownloadSetting"}, // 11.0.0+ {10, nullptr, "CreateForbiddenSaveDataInidication"}, - {11, nullptr, "StopAutonomyTaskExecution"}, + {11, D<&IDaemonController::StopAutonomyTaskExecution>, "StopAutonomyTaskExecution"}, {12, D<&IDaemonController::GetAutonomyTaskStatus>, "GetAutonomyTaskStatus"}, {13, nullptr, "Unknown13_20_0_0_Plus"}, // 20.0.0+ }; @@ -92,13 +92,17 @@ Result IDaemonController::SetGlobalAutoDownloadSetting(bool is_enabled, Common:: R_SUCCEED(); } -Result IDaemonController::GetAutonomyTaskStatus(Out out_state, Common::UUID user_id, - u64 application_id) { - LOG_INFO(Service_OLSC, "called, user_id={} application_id={:016X}", user_id.FormattedString(), - application_id); - AppKey key{user_id, application_id}; - const auto it = autonomy_task_status_.find(key); - *out_state = (it != autonomy_task_status_.end()) ? it->second : 0; // default idle +Result IDaemonController::StopAutonomyTaskExecution(Out> out_stopper) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + *out_stopper = std::make_shared(system); + R_SUCCEED(); +} + +Result IDaemonController::GetAutonomyTaskStatus(Out out_status, Common::UUID user_id) { + LOG_INFO(Service_OLSC, "called, user_id={}", user_id.FormattedString()); + + *out_status = 0; R_SUCCEED(); } diff --git a/src/core/hle/service/olsc/daemon_controller.h b/src/core/hle/service/olsc/daemon_controller.h index 7e5416562e..4a4b89317b 100644 --- a/src/core/hle/service/olsc/daemon_controller.h +++ b/src/core/hle/service/olsc/daemon_controller.h @@ -1,5 +1,4 @@ -#pragma once -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -9,6 +8,7 @@ #include "common/uuid.h" #include "core/hle/service/cmif_types.h" +#include "core/hle/service/olsc/stopper_object.h" #include "core/hle/service/service.h" namespace Service::OLSC { @@ -32,7 +32,9 @@ private: Result GetGlobalAutoDownloadSetting(Out out_is_enabled, Common::UUID user_id); Result SetGlobalAutoDownloadSetting(bool is_enabled, Common::UUID user_id); - Result GetAutonomyTaskStatus(Out out_state, Common::UUID user_id, u64 application_id); + Result StopAutonomyTaskExecution(Out> out_stopper); + + Result GetAutonomyTaskStatus(Out out_status, Common::UUID user_id); // Internal in-memory state to back the above APIs struct AppKey { diff --git a/src/core/hle/service/olsc/native_handle_holder.cpp b/src/core/hle/service/olsc/native_handle_holder.cpp index 3cb5d7b114..d381714bc3 100644 --- a/src/core/hle/service/olsc/native_handle_holder.cpp +++ b/src/core/hle/service/olsc/native_handle_holder.cpp @@ -1,13 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/olsc/native_handle_holder.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" + namespace Service::OLSC { INativeHandleHolder::INativeHandleHolder(Core::System& system_) - : ServiceFramework{system_, "INativeHandleHolder"} { + : ServiceFramework{system_, "INativeHandleHolder"}, service_context{system_, "OLSC"} { + event = service_context.CreateEvent("OLSC::INativeHandleHolder"); // clang-format off static const FunctionInfo functions[] = { {0, D<&INativeHandleHolder::GetNativeHandle>, "GetNativeHandle"}, @@ -17,11 +24,19 @@ INativeHandleHolder::INativeHandleHolder(Core::System& system_) RegisterHandlers(functions); } -INativeHandleHolder::~INativeHandleHolder() = default; +INativeHandleHolder::~INativeHandleHolder() { + service_context.CloseEvent(event); + event = nullptr; +} Result INativeHandleHolder::GetNativeHandle(OutCopyHandle out_event) { LOG_WARNING(Service_OLSC, "(STUBBED) called"); - *out_event = nullptr; + if (event) { + event->Signal(); + *out_event = std::addressof(event->GetReadableEvent()); + } else { + *out_event = nullptr; + } R_SUCCEED(); } diff --git a/src/core/hle/service/olsc/native_handle_holder.h b/src/core/hle/service/olsc/native_handle_holder.h index a44754c201..985c60d1cb 100644 --- a/src/core/hle/service/olsc/native_handle_holder.h +++ b/src/core/hle/service/olsc/native_handle_holder.h @@ -1,11 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" +#include "core/hle/service/kernel_helpers.h" + namespace Kernel { class KReadableEvent; +class KEvent; } namespace Service::OLSC { @@ -17,6 +23,9 @@ public: private: Result GetNativeHandle(OutCopyHandle out_event); + + Service::KernelHelpers::ServiceContext service_context; + Kernel::KEvent* event{}; }; } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/olsc_service_for_system_service.cpp b/src/core/hle/service/olsc/olsc_service_for_system_service.cpp index 98b4a52118..245bbd5c6b 100644 --- a/src/core/hle/service/olsc/olsc_service_for_system_service.cpp +++ b/src/core/hle/service/olsc/olsc_service_for_system_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -28,7 +28,7 @@ IOlscServiceForSystemService::IOlscServiceForSystemService(Core::System& system_ {102, nullptr, "RemoveLastErrorInfoOld"}, {103, nullptr, "GetLastErrorInfo"}, {104, nullptr, "GetLastErrorEventHolder"}, - {105, nullptr, "GetLastTransferTaskErrorInfo"}, + {105, D<&IOlscServiceForSystemService::GetTransferTaskErrorInfo>, "GetTransferTaskErrorInfo"}, {200, D<&IOlscServiceForSystemService::GetDataTransferPolicy>, "GetDataTransferPolicy"}, {201, nullptr, "DeleteDataTransferPolicyCache"}, {202, nullptr, "Unknown202"}, @@ -116,6 +116,24 @@ Result IOlscServiceForSystemService::GetDataTransferPolicy( R_SUCCEED(); } +Result IOlscServiceForSystemService::GetTransferTaskErrorInfo(Out out_info, + Common::UUID uuid, u64 application_id) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, uuid={} application_id={:016X}", + uuid.FormattedString(), application_id); + + TransferTaskErrorInfo info{}; + info.uid = uuid; + info.application_id = application_id; + info.unknown_0x18 = 0; + info.reserved_0x19.fill(0); + info.unknown_0x20 = 0; + info.error_code = 0; + info.reserved_0x2C = 0; + + *out_info = info; + R_SUCCEED(); +} + Result IOlscServiceForSystemService::GetOlscServiceForSystemService( Out> out_interface) { LOG_INFO(Service_OLSC, "called"); diff --git a/src/core/hle/service/olsc/olsc_service_for_system_service.h b/src/core/hle/service/olsc/olsc_service_for_system_service.h index 97f791b74a..447e37005e 100644 --- a/src/core/hle/service/olsc/olsc_service_for_system_service.h +++ b/src/core/hle/service/olsc/olsc_service_for_system_service.h @@ -1,9 +1,12 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "common/uuid.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" @@ -18,6 +21,17 @@ struct DataTransferPolicy { u8 download_policy; }; +struct TransferTaskErrorInfo { + Common::UUID uid; + u64 application_id; + u8 unknown_0x18; + std::array reserved_0x19; + u64 unknown_0x20; + u32 error_code; + u32 reserved_0x2C; +}; +static_assert(sizeof(TransferTaskErrorInfo) == 0x30, "TransferTaskErrorInfo has incorrect size."); + class IOlscServiceForSystemService final : public ServiceFramework { public: explicit IOlscServiceForSystemService(Core::System& system_); @@ -29,7 +43,9 @@ private: Result GetRemoteStorageController(Out> out_interface); Result GetDaemonController(Out> out_interface); Result GetDataTransferPolicy(Out out_policy, u64 application_id); - Result GetOlscServiceForSystemService(Out> out_interface); + Result GetTransferTaskErrorInfo(Out out_info, Common::UUID uid, u64 application_id); + Result GetOlscServiceForSystemService( + Out> out_interface); }; } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/remote_storage_controller.cpp b/src/core/hle/service/olsc/remote_storage_controller.cpp index 7e04b7c042..817097b426 100644 --- a/src/core/hle/service/olsc/remote_storage_controller.cpp +++ b/src/core/hle/service/olsc/remote_storage_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -24,11 +24,11 @@ IRemoteStorageController::IRemoteStorageController(Core::System& system_) {11, nullptr, "CreateDeleteDataTask"}, {12, nullptr, "DeleteSeriesInfo"}, {13, nullptr, "CreateRegisterNotificationTokenTask"}, - {14, nullptr, "UpdateSeriesInfo"}, + {14, D<&IRemoteStorageController::GetDataNewnessByApplicationId>, "GetDataNewnessByApplicationId"}, {15, nullptr, "RegisterUploadSaveDataTransferTaskForAutonomyRegistration"}, {16, nullptr, "CreateCleanupToDeleteSaveDataArchiveInfoTask"}, {17, nullptr, "ListDataInfo"}, - {18, nullptr, "GetDataInfo"}, + {18, D<&IRemoteStorageController::GetDataInfo>, "GetDataInfoV1"}, {19, nullptr, "GetDataInfoCacheUpdateNativeHandleHolder"}, {20, nullptr, "CreateSaveDataArchiveInfoCacheForSaveDataBackupUpdationTask"}, {21, nullptr, "ListSecondarySaves"}, @@ -37,7 +37,7 @@ IRemoteStorageController::IRemoteStorageController(Core::System& system_) {24, nullptr, "GetSecondarySaveDataInfo"}, {25, nullptr, "RegisterDownloadSaveDataTransferTaskForAutonomyRegistration"}, {26, nullptr, "Unknown26"}, //20.0.0+ - {27, nullptr, "Unknown27"}, //20.0.0+ + {27, D<&IRemoteStorageController::GetDataInfo>, "GetDataInfoV2"}, //20.0.0+ {28, nullptr, "Unknown28"}, //20.0.0+ {29, nullptr, "Unknown29"}, //21.0.0+ {800, nullptr, "Unknown800"}, //20.0.0+ @@ -60,4 +60,17 @@ Result IRemoteStorageController::GetSecondarySave(Out out_has_secondary_sa R_SUCCEED(); } +Result IRemoteStorageController::GetDataNewnessByApplicationId(Out out_newness, + u64 application_id) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, application_id={:016X}", application_id); + *out_newness = 0; + R_SUCCEED(); +} + +Result IRemoteStorageController::GetDataInfo(Out> out_data, u64 application_id) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, application_id={:016X}", application_id); + out_data->fill(0); + R_SUCCEED(); +} + } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/remote_storage_controller.h b/src/core/hle/service/olsc/remote_storage_controller.h index e7a0b52442..bae1e8efb5 100644 --- a/src/core/hle/service/olsc/remote_storage_controller.h +++ b/src/core/hle/service/olsc/remote_storage_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,6 +17,8 @@ public: private: Result GetSecondarySave(Out out_has_secondary_save, Out> out_unknown, u64 application_id); + Result GetDataNewnessByApplicationId(Out out_newness, u64 application_id); + Result GetDataInfo(Out> out_data, u64 application_id); }; } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/stopper_object.cpp b/src/core/hle/service/olsc/stopper_object.cpp new file mode 100644 index 0000000000..b4baf1236b --- /dev/null +++ b/src/core/hle/service/olsc/stopper_object.cpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/olsc/stopper_object.h" + +namespace Service::OLSC { + +IStopperObject::IStopperObject(Core::System& system_) : ServiceFramework{system_, "IStopperObject"} { + // TODO(Maufeat): If ever needed, add cmds +} + +IStopperObject::~IStopperObject() = default; + +} // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/stopper_object.h b/src/core/hle/service/olsc/stopper_object.h new file mode 100644 index 0000000000..3a23f6bdda --- /dev/null +++ b/src/core/hle/service/olsc/stopper_object.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" + +namespace Service::OLSC { + +class IStopperObject final : public ServiceFramework { +public: + explicit IStopperObject(Core::System& system_); + ~IStopperObject() override; +}; + +} // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/transfer_task_list_controller.cpp b/src/core/hle/service/olsc/transfer_task_list_controller.cpp index 093bf36c0e..0f9042bf01 100644 --- a/src/core/hle/service/olsc/transfer_task_list_controller.cpp +++ b/src/core/hle/service/olsc/transfer_task_list_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -6,6 +6,7 @@ #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/olsc/native_handle_holder.h" +#include "core/hle/service/olsc/stopper_object.h" #include "core/hle/service/olsc/transfer_task_list_controller.h" namespace Service::OLSC { @@ -22,7 +23,7 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_) {5, D<&ITransferTaskListController::GetTransferTaskEndEventNativeHandleHolder>, "GetTransferTaskEndEventNativeHandleHolder"}, {6, nullptr, "GetTransferTaskProgressForOcean"}, {7, nullptr, "GetTransferTaskLastResultForOcean"}, - {8, nullptr, "StopNextTransferTaskExecution"}, + {8, D<&ITransferTaskListController::StopNextTransferTaskExecution>, "StopNextTransferTaskExecution"}, {9, D<&ITransferTaskListController::GetTransferTaskStartEventNativeHandleHolder>, "GetTransferTaskStartEventNativeHandleHolder"}, {10, nullptr, "SuspendTransferTaskForOcean"}, {11, nullptr, "GetCurrentTransferTaskInfoForOcean"}, @@ -30,7 +31,7 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_) {13, nullptr, "CancelCurrentRepairTransferTask"}, {14, nullptr, "GetRepairTransferTaskProgress"}, {15, nullptr, "EnsureExecutableForRepairTransferTask"}, - {16, nullptr, "GetTransferTaskCount"}, + {16, D<&ITransferTaskListController::GetTransferTaskCount>, "GetTransferTaskCount"}, {17, nullptr, "GetTransferTaskInfo"}, {18, nullptr, "ListTransferTaskInfo"}, {19, nullptr, "DeleteTransferTask"}, @@ -38,8 +39,8 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_) {21, nullptr, "GetTransferTaskProgress"}, {22, nullptr, "GetTransferTaskLastResult"}, {23, nullptr, "SuspendTransferTask"}, - {24, nullptr, "GetCurrentTransferTaskInfo"}, - {25, nullptr, "Unknown25"}, //20.1.0+ + {24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"}, + {25, D<&ITransferTaskListController::FindTransferTaskInfo>, "FindTransferTaskInfo"}, {26, nullptr, "Unknown26"}, //20.1.0+ {27, nullptr, "Unknown27"}, //20.1.0+ {28, nullptr, "Unknown28"}, //20.1.0+ @@ -60,6 +61,13 @@ Result ITransferTaskListController::GetTransferTaskEndEventNativeHandleHolder( R_SUCCEED(); } +Result ITransferTaskListController::StopNextTransferTaskExecution( + Out> out_stopper) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + *out_stopper = std::make_shared(system); + R_SUCCEED(); +} + Result ITransferTaskListController::GetTransferTaskStartEventNativeHandleHolder( Out> out_holder) { LOG_WARNING(Service_OLSC, "(STUBBED) called"); @@ -67,4 +75,24 @@ Result ITransferTaskListController::GetTransferTaskStartEventNativeHandleHolder( R_SUCCEED(); } +Result ITransferTaskListController::GetCurrentTransferTaskInfo(Out> out_info, + u8 unknown) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, unknown={:#x}", unknown); + out_info->fill(0); + R_SUCCEED(); +} + +Result ITransferTaskListController::FindTransferTaskInfo(Out> out_info, + InBuffer in) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, in_size={}", in.size()); + out_info->fill(0); + R_SUCCEED(); +} + +Result ITransferTaskListController::GetTransferTaskCount(Out out_count, u8 unknown) { + LOG_WARNING(Service_OLSC, "(STUBBED) called, unknown={:#x}", unknown); + *out_count = 0; + R_SUCCEED(); +} + } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/transfer_task_list_controller.h b/src/core/hle/service/olsc/transfer_task_list_controller.h index e6864e5d6f..72dbf610d8 100644 --- a/src/core/hle/service/olsc/transfer_task_list_controller.h +++ b/src/core/hle/service/olsc/transfer_task_list_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -10,6 +10,7 @@ namespace Service::OLSC { class INativeHandleHolder; +class IStopperObject; class ITransferTaskListController final : public ServiceFramework { public: @@ -19,6 +20,10 @@ public: private: Result GetTransferTaskEndEventNativeHandleHolder(Out> out_holder); Result GetTransferTaskStartEventNativeHandleHolder(Out> out_holder); + Result StopNextTransferTaskExecution(Out> out_stopper); + Result GetTransferTaskCount(Out out_count, u8 unknown); + Result GetCurrentTransferTaskInfo(Out> out_info, u8 unknown); + Result FindTransferTaskInfo(Out> out_info, InBuffer in); }; } // namespace Service::OLSC diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp index a712353cb1..c2ddbcbc51 100644 --- a/src/core/hle/service/pctl/parental_control_service.cpp +++ b/src/core/hle/service/pctl/parental_control_service.cpp @@ -292,7 +292,7 @@ Result IParentalControlService::EndFreeCommunication() { } Result IParentalControlService::IsFreeCommunicationAvailable() { - LOG_WARNING(Service_PCTL, "(STUBBED) called"); + LOG_DEBUG(Service_PCTL, "(STUBBED) called"); if (!CheckFreeCommunicationPermissionImpl()) { R_THROW(PCTL::ResultNoFreeCommunication); diff --git a/src/core/hle/service/psc/time/errors.h b/src/core/hle/service/psc/time/errors.h index 6d833a0060..3f52a70ba5 100644 --- a/src/core/hle/service/psc/time/errors.h +++ b/src/core/hle/service/psc/time/errors.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,18 +10,18 @@ namespace Service::PSC::Time { -constexpr Result ResultPermissionDenied{ErrorModule::Time, 1}; -constexpr Result ResultClockMismatch{ErrorModule::Time, 102}; -constexpr Result ResultClockUninitialized{ErrorModule::Time, 103}; -constexpr Result ResultTimeNotFound{ErrorModule::Time, 200}; -constexpr Result ResultOverflow{ErrorModule::Time, 201}; -constexpr Result ResultFailed{ErrorModule::Time, 801}; -constexpr Result ResultInvalidArgument{ErrorModule::Time, 901}; -constexpr Result ResultTimeZoneOutOfRange{ErrorModule::Time, 902}; -constexpr Result ResultTimeZoneParseFailed{ErrorModule::Time, 903}; -constexpr Result ResultRtcTimeout{ErrorModule::Time, 988}; -constexpr Result ResultTimeZoneNotFound{ErrorModule::Time, 989}; -constexpr Result ResultNotImplemented{ErrorModule::Time, 990}; -constexpr Result ResultAlarmNotRegistered{ErrorModule::Time, 1502}; +constexpr Result ResultPermissionDenied{ErrorModule::TimeService, 1}; +constexpr Result ResultClockMismatch{ErrorModule::TimeService, 102}; +constexpr Result ResultClockUninitialized{ErrorModule::TimeService, 103}; +constexpr Result ResultTimeNotFound{ErrorModule::TimeService, 200}; +constexpr Result ResultOverflow{ErrorModule::TimeService, 201}; +constexpr Result ResultFailed{ErrorModule::TimeService, 801}; +constexpr Result ResultInvalidArgument{ErrorModule::TimeService, 901}; +constexpr Result ResultTimeZoneOutOfRange{ErrorModule::TimeService, 902}; +constexpr Result ResultTimeZoneParseFailed{ErrorModule::TimeService, 903}; +constexpr Result ResultRtcTimeout{ErrorModule::TimeService, 988}; +constexpr Result ResultTimeZoneNotFound{ErrorModule::TimeService, 989}; +constexpr Result ResultNotImplemented{ErrorModule::TimeService, 990}; +constexpr Result ResultAlarmNotRegistered{ErrorModule::TimeService, 1502}; } // namespace Service::PSC::Time diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index d4932fc4ee..fd758d4915 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -643,7 +643,14 @@ Errno BSD::ConnectImpl(s32 fd, std::span addr) { UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn)); auto addr_in = GetValue(addr); - return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); + const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); + + if (result == Errno::ISCONN) { + LOG_DEBUG(Service, "returned ISCONN - socket already connected"); + return Errno::SUCCESS; + } + + return result; } Errno BSD::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { @@ -750,7 +757,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span(SocketLevel::SOCKET)) { - UNIMPLEMENTED_MSG("Unknown setsockopt level"); + LOG_WARNING(Service, "(STUBBED) setsockopt with level={}, optname={}", level, optname); return Errno::SUCCESS; } diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 491b76d481..241f2971ca 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,7 +37,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na {12, nullptr, "GetDeviceId"}, {13, nullptr, "DeleteSettings"}, {14, nullptr, "ImportSettings"}, - {15, nullptr, "SetChangeEnvironmentIdentifierDisabled"}, + {15, &NSD::SetChangeEnvironmentIdentifierDisabled, "SetChangeEnvironmentIdentifierDisabled"}, {20, &NSD::Resolve, "Resolve"}, {21, &NSD::ResolveEx, "ResolveEx"}, {30, nullptr, "GetNasServiceSetting"}, @@ -77,6 +80,16 @@ static Result ResolveCommon(const std::string& fqdn_in, std::array& return ResultSuccess; } +void NSD::SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const bool disabled = rp.Pop(); + + LOG_WARNING(Service, "(STUBBED) called, disabled={}", disabled); + + IPC::ResponseBuilder rb{ctx, 1}; + rb.Push(ResultSuccess); +} + void NSD::Resolve(HLERequestContext& ctx) { const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index b0cfec507f..e4a4588467 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -17,6 +20,7 @@ public: ~NSD() override; private: + void SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx); void Resolve(HLERequestContext& ctx); void ResolveEx(HLERequestContext& ctx); void GetEnvironmentIdentifier(HLERequestContext& ctx); diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 0093347eb8..b72bf53c16 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -29,6 +29,7 @@ enum class Errno : u32 { TIMEDOUT = 110, CONNREFUSED = 111, INPROGRESS = 115, + ISCONN = 106, }; enum class GetAddrInfoError : s32 { @@ -71,6 +72,8 @@ enum class Protocol : u32 { }; enum class SocketLevel : u32 { + IP = 0, + TCP = 6, SOCKET = 0xffff, // i.e. SOL_SOCKET }; diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 21bb3e7764..9d82d347eb 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,6 +8,7 @@ #include "common/assert.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/sockets/sockets_translate.h" #include "core/internal_network/network.h" @@ -37,6 +41,8 @@ Errno Translate(Network::Errno value) { return Errno::CONNRESET; case Network::Errno::INPROGRESS: return Errno::INPROGRESS; + case Network::Errno::ISCONN: + return Errno::ISCONN; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", value); return Errno::SUCCESS; @@ -259,9 +265,10 @@ PollEvents Translate(Network::PollEvents flags) { } Network::SockAddrIn Translate(SockAddrIn value) { - // Note: 6 is incorrect, but can be passed by homebrew (because libnx sets - // sin_len to 6 when deserializing getaddrinfo results). - ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6); + if (value.len != 0 && value.len != sizeof(value) && value.len != 6) { + LOG_WARNING(Service, "Unexpected SockAddrIn len={}, expected 0, {}, or 6", + value.len, sizeof(value)); + } return { .family = Translate(static_cast(value.family)), diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 5a1ae0dbc9..b66c686b2a 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -116,6 +116,8 @@ public: RegisterHandlers(functions); + backend->SetVerifyOption(verify_option); + shared_data->connection_count++; } @@ -150,6 +152,7 @@ private: std::shared_ptr socket; std::vector next_alpn_proto; bool did_handshake = false; + u32 verify_option = 0; Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { LOG_DEBUG(Service_SSL, "called, fd={}", fd); @@ -190,7 +193,9 @@ private: Result SetVerifyOptionImpl(u32 option) { ASSERT(!did_handshake); - LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option); + LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option); + verify_option = 0; + backend->SetVerifyOption(0); return ResultSuccess; } diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index a2ec8e6947..a3127342a2 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,6 +37,7 @@ public: virtual ~SSLConnectionBackend() {} virtual void SetSocket(std::shared_ptr socket) = 0; virtual Result SetHostName(const std::string& hostname) = 0; + virtual void SetVerifyOption(u32 option) = 0; virtual Result DoHandshake() = 0; virtual Result Read(size_t* out_size, std::span data) = 0; virtual Result Write(size_t* out_size, std::span data) = 0; diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index 02d0b8fba7..f0e05266ba 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -158,9 +158,11 @@ public: } Result SetHostName(const std::string& hostname) override { - if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification - LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); - return CheckOpenSSLErrors(); + if (!skip_cert_verification) { + if (!SSL_set1_host(ssl, hostname.c_str())) { + LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); + return CheckOpenSSLErrors(); + } } if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); @@ -169,15 +171,32 @@ public: return ResultSuccess; } + void SetVerifyOption(u32 option) override { + skip_cert_verification = (option == 0); + LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, + skip_cert_verification); + if (skip_cert_verification) { + SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + SSL_set1_host(ssl, nullptr); + SSL_set_hostflags(ssl, 0); + } else { + SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); + } + } + Result DoHandshake() override { SSL_set_verify_result(ssl, X509_V_OK); const int ret = SSL_do_handshake(ssl); - const long verify_result = SSL_get_verify_result(ssl); - if (verify_result != X509_V_OK) { - LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}", - X509_verify_cert_error_string(verify_result)); - return CheckOpenSSLErrors(); + + if (!skip_cert_verification) { + const long verify_result = SSL_get_verify_result(ssl); + if (verify_result != X509_V_OK) { + LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}", + X509_verify_cert_error_string(verify_result)); + return CheckOpenSSLErrors(); + } } + if (ret <= 0) { const int ssl_err = SSL_get_error(ssl, ret); if (ssl_err == SSL_ERROR_ZERO_RETURN || @@ -328,6 +347,7 @@ public: SSL* ssl = nullptr; BIO* bio = nullptr; bool got_read_eof = false; + bool skip_cert_verification = false; std::shared_ptr socket; }; diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index 212057cfc9..2b68f29eaf 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -86,6 +89,12 @@ public: return ResultSuccess; } + void SetVerifyOption(u32 option) override { + skip_cert_verification = (option == 0); + LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, + skip_cert_verification); + } + Result DoHandshake() override { while (1) { Result r; @@ -172,10 +181,15 @@ public: } Result CallInitializeSecurityContext() { - const unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | - ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT | - ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM | - ISC_REQ_USE_SUPPLIED_CREDS; + unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | + ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM | + ISC_REQ_USE_SUPPLIED_CREDS; + + if (skip_cert_verification) { + req |= ISC_REQ_MANUAL_CRED_VALIDATION; + } + unsigned long attr; // https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel std::array input_buffers{{ @@ -309,7 +323,7 @@ public: } while (1) { if (!cleartext_read_buf.empty()) { - *out_size = std::min(cleartext_read_buf.size(), data.size()); + *out_size = (std::min)(cleartext_read_buf.size(), data.size()); std::memcpy(data.data(), cleartext_read_buf.data(), *out_size); cleartext_read_buf.erase(cleartext_read_buf.begin(), cleartext_read_buf.begin() + *out_size); @@ -533,6 +547,7 @@ public: std::vector cleartext_write_buf; bool got_read_eof = false; + bool skip_cert_verification = false; size_t read_buf_fill_size = 0; }; diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index c48914f640..65f81be631 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -98,8 +101,23 @@ public: return ResultSuccess; } + void SetVerifyOption(u32 option) override { + skip_cert_verification = (option == 0); + LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, + skip_cert_verification); + if (skip_cert_verification) { + SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); + } + } + Result DoHandshake() override { OSStatus status = SSLHandshake(context); + + if (skip_cert_verification && status == errSSLServerAuthCompleted) { + LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested"); + status = SSLHandshake(context); + } + return HandleReturn("SSLHandshake", 0, status); } @@ -201,6 +219,7 @@ public: private: CFReleaser context = nullptr; bool got_read_eof = false; + bool skip_cert_verification = false; std::shared_ptr socket; }; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 35f2157761..f16da5a3d8 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -157,6 +157,8 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { return Errno::TIMEDOUT; case WSAEINPROGRESS: return Errno::INPROGRESS; + case WSAEISCONN: + return Errno::ISCONN; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", e); return Errno::OTHER; @@ -296,6 +298,8 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { return Errno::TIMEDOUT; case EINPROGRESS: return Errno::INPROGRESS; + case EISCONN: + return Errno::ISCONN; default: UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e)); return Errno::OTHER; @@ -872,7 +876,9 @@ std::pair Socket::SendTo(u32 flags, std::span message, Errno Socket::Close() { [[maybe_unused]] const int result = closesocket(fd); - ASSERT(result == 0); + if (result != 0) { + LOG_WARNING(Network, "closesocket failed, socket may already be closed"); + } fd = INVALID_SOCKET; return Errno::SUCCESS; diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index 5df28911f8..ce259c0e66 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -48,6 +48,7 @@ enum class Errno { TIMEDOUT, MSGSIZE, INPROGRESS, + ISCONN, OTHER, }; diff --git a/src/core/launch_timestamp_cache.cpp b/src/core/launch_timestamp_cache.cpp new file mode 100644 index 0000000000..95b6bc8c41 --- /dev/null +++ b/src/core/launch_timestamp_cache.cpp @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/launch_timestamp_cache.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/fs/fs.h" +#include "common/fs/file.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +namespace Core::LaunchTimestampCache { +namespace { + +using CacheMap = std::unordered_map; +using CountMap = std::unordered_map; + +std::mutex g_mutex; +CacheMap g_cache; +CountMap g_counts; +bool g_loaded = false; + +std::filesystem::path GetCachePath() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "launched.json"; +} + +std::optional ReadFileToString(const std::filesystem::path& path) { + const std::ifstream file{path, std::ios::in | std::ios::binary}; + if (!file) { + return std::nullopt; + } + std::ostringstream ss; + ss << file.rdbuf(); + return ss.str(); +} + +bool WriteStringToFile(const std::filesystem::path& path, const std::string& data) { + if (!Common::FS::CreateParentDirs(path)) { + return false; + } + std::ofstream file{path, std::ios::out | std::ios::binary | std::ios::trunc}; + if (!file) { + return false; + } + file.write(data.data(), static_cast(data.size())); + return static_cast(file); +} + +void Load() { + if (g_loaded) { + return; + } + + g_loaded = true; + + const auto path = GetCachePath(); + if (!std::filesystem::exists(path)) { + return; + } + + const auto data = ReadFileToString(path); + if (!data) { + LOG_WARNING(Core, "Failed to read launch timestamp cache: {}", + Common::FS::PathToUTF8String(path)); + return; + } + + try { + const auto json = nlohmann::json::parse(data->data(), data->data() + data->size()); + if (!json.is_object()) { + return; + } + for (auto it = json.begin(); it != json.end(); ++it) { + const auto key_str = it.key(); + const auto value = it.value(); + u64 key{}; + try { + key = std::stoull(key_str, nullptr, 16); + } catch (...) { + continue; + } + if (value.is_object()) { + if (value.contains("timestamp") && value["timestamp"].is_number_integer()) { + g_cache[key] = value["timestamp"].get(); + } + if (value.contains("launch_count") && value["launch_count"].is_number_unsigned()) { + g_counts[key] = value["launch_count"].get(); + } + } else if (value.is_number_integer()) { + // Legacy format: raw timestamp only + g_cache[key] = value.get(); + } + } + } catch (const std::exception& e) { + LOG_WARNING(Core, "Failed to parse launch timestamp cache: {}", e.what()); + } +} + +void Save() { + nlohmann::json json = nlohmann::json::object(); + for (const auto& [key, value] : g_cache) { + nlohmann::json entry = nlohmann::json::object(); + entry["timestamp"] = value; + const auto count_it = g_counts.find(key); + entry["launch_count"] = count_it != g_counts.end() ? count_it->second : 0; + json[fmt::format("{:016X}", key)] = entry; + } + + const auto path = GetCachePath(); + if (!WriteStringToFile(path, json.dump(4))) { + LOG_WARNING(Core, "Failed to write launch timestamp cache: {}", + Common::FS::PathToUTF8String(path)); + } +} + +s64 NowSeconds() { + return std::time(nullptr); +} + +} // namespace + +void SaveLaunchTimestamp(u64 title_id) { + std::scoped_lock lk{g_mutex}; + Load(); + g_cache[title_id] = NowSeconds(); + g_counts[title_id] = g_counts[title_id] + 1; + Save(); +} + +s64 GetLaunchTimestamp(u64 title_id) { + std::scoped_lock lk{g_mutex}; + Load(); + const auto it = g_cache.find(title_id); + if (it != g_cache.end()) { + return it->second; + } + // we need a timestamp, i decided on 01/01/2026 00:00 + return 1767225600; +} + +u64 GetLaunchCount(u64 title_id) { + std::scoped_lock lk{g_mutex}; + Load(); + const auto it = g_counts.find(title_id); + if (it != g_counts.end()) { + return it->second; + } + return 0; +} + +} // namespace Core::LaunchTimestampCache diff --git a/src/core/launch_timestamp_cache.h b/src/core/launch_timestamp_cache.h new file mode 100644 index 0000000000..12d8deffdc --- /dev/null +++ b/src/core/launch_timestamp_cache.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Core::LaunchTimestampCache { + +void SaveLaunchTimestamp(u64 title_id); +s64 GetLaunchTimestamp(u64 title_id); +u64 GetLaunchCount(u64 title_id); + +} // namespace Core::LaunchTimestampCache diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index e05757b6ca..1558d76b35 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -38,7 +38,6 @@ CMAKE_DEPENDENT_OPTION(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 c option(DYNARMIC_INSTALL "Install dynarmic headers and CMake files" OFF) option(DYNARMIC_USE_BUNDLED_EXTERNALS "Use all bundled externals (useful when e.g. cross-compiling)" OFF) -option(DYNARMIC_ENABLE_LTO "Enable LTO" OFF) # Set hard requirements for C++ set(CMAKE_CXX_STANDARD 20) @@ -115,10 +114,6 @@ endif() find_package(Boost 1.57 REQUIRED) find_package(fmt 8 CONFIG) - -# Pull in externals CMakeLists for libs where available -add_subdirectory(externals) - find_package(mcl 0.1.12 REQUIRED) if ("arm64" IN_LIST ARCHITECTURE OR DYNARMIC_TESTS) diff --git a/src/dynarmic/externals/CMakeLists.txt b/src/dynarmic/externals/CMakeLists.txt deleted file mode 100644 index 1956499464..0000000000 --- a/src/dynarmic/externals/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -include(CPMUtil) - -# Always build externals as static libraries, even when dynarmic is built as shared -set(BUILD_SHARED_LIBS OFF) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set_property(DIRECTORY PROPERTY EXCLUDE_FROM_ALL ON) - -# Allow options shadowing with normal variables when subproject use old cmake policy -set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) - -# Disable tests in all externals supporting the standard option name -set(BUILD_TESTING OFF) - -# For libraries that already come with a CMakeLists file, -# simply add the directory to that file as a subdirectory -# to have CMake automatically recognize them. - -# biscuit - -if ("riscv" IN_LIST ARCHITECTURE) - AddJsonPackage( - NAME biscuit - BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} - ) -endif() - -# mcl -AddJsonPackage( - NAME mcl - BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} -) diff --git a/src/dynarmic/externals/cpmfile.json b/src/dynarmic/externals/cpmfile.json deleted file mode 100644 index 584e1f9115..0000000000 --- a/src/dynarmic/externals/cpmfile.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "biscuit": { - "repo": "lioncash/biscuit", - "tag": "v%VERSION%", - "hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a", - "version": "0.9.1", - "git_version": "0.19.0" - }, - "mcl": { - "version": "0.1.12", - "repo": "azahar-emu/mcl", - "sha": "7b08d83418", - "hash": "9c6ba624cb22ef622f78046a82abb99bf5026284ba17dfacaf46ac842cbd3b0f515f5ba45a1598c7671318a78a2e648db72ce8d10e7537f34e39800bdcb57694", - "options": [ - "MCL_INSTALL OFF" - ], - "patches": [ - "0001-assert-macro.patch" - ] - } -} diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index 9575eaab6f..f2ea9de455 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -395,7 +395,3 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0) endif() target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) - -if (DYNARMIC_ENABLE_LTO) - set_property(TARGET dynarmic PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) -endif() diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp index 5205287850..75c898a1cd 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp @@ -268,7 +268,10 @@ std::pair InlinePageTableEmitVAddrLookup(oaknut::Cod code.B(NE, *fallback); } - code.LDR(Xscratch0, Xpagetable, Xscratch0, LSL, ctx.conf.page_table_log2_stride); + // index = index << log2 + code.LSL(Xscratch0, Xscratch0, ctx.conf.page_table_log2_stride); + // load x0 = *<(u8*)pagetable + index> + code.LDR(Xscratch0, Xpagetable, Xscratch0); if (ctx.conf.page_table_pointer_mask_bits != 0) { const u64 mask = u64(~u64(0)) << ctx.conf.page_table_pointer_mask_bits; diff --git a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp index 6cb0abc4ef..c82c815cea 100644 --- a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp +++ b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later /* This file is part of the dynarmic project. @@ -1227,32 +1227,28 @@ static void DeadCodeElimination(IR::Block& block) { } static void IdentityRemovalPass(IR::Block& block) { - boost::container::small_vector to_invalidate; - - auto iter = block.begin(); - while (iter != block.end()) { - IR::Inst& inst = *iter; - - const size_t num_args = inst.NumArgs(); - for (size_t i = 0; i < num_args; i++) { - while (true) { - IR::Value arg = inst.GetArg(i); - if (!arg.IsIdentity()) - break; - inst.SetArg(i, arg.GetInst()->GetArg(0)); + boost::container::small_vector to_invalidate; + for (auto it = block.begin(); it != block.end();) { + const size_t num_args = it->NumArgs(); + for (size_t i = 0; i < num_args; ++i) { + IR::Value arg = it->GetArg(i); + if (arg.IsIdentity()) { + do { + arg = arg.GetInst()->GetArg(0); + } while (arg.IsIdentity()); + it->SetArg(i, arg); } } - if (inst.GetOpcode() == IR::Opcode::Identity || inst.GetOpcode() == IR::Opcode::Void) { - iter = block.Instructions().erase(inst); - to_invalidate.push_back(&inst); + if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) { + to_invalidate.push_back(&*it); + it = block.Instructions().erase(it); } else { - ++iter; + ++it; } } - for (IR::Inst* inst : to_invalidate) { + for (IR::Inst* const inst : to_invalidate) inst->Invalidate(); - } } static void NamingPass(IR::Block& block) { diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 911f3ebdca..b8a282b234 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt @@ -12,16 +12,14 @@ add_library(frontend_common STATIC firmware_manager.cpp data_manager.h data_manager.cpp play_time_manager.cpp - play_time_manager.h -) + play_time_manager.h) if (ENABLE_UPDATE_CHECKER) target_link_libraries(frontend_common PRIVATE httplib::httplib) target_link_libraries(frontend_common PRIVATE nlohmann_json::nlohmann_json) target_sources(frontend_common PRIVATE update_checker.cpp - update_checker.h - ) + update_checker.h) if (ENABLE_OPENSSL) target_compile_definitions(frontend_common PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index fdb7ac0833..0e304a87d6 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -423,12 +423,12 @@ void Config::ReadValues() { ReadDataStorageValues(); ReadDebuggingValues(); ReadDisabledAddOnValues(); - ReadNetworkValues(); ReadServiceValues(); ReadWebServiceValues(); ReadMiscellaneousValues(); ReadLibraryAppletValues(); } + ReadNetworkValues(); ReadControlValues(); ReadCoreValues(); ReadCpuValues(); @@ -522,13 +522,13 @@ void Config::SaveValues() { SaveDataStorageValues(); SaveDebuggingValues(); SaveDisabledAddOnValues(); - SaveNetworkValues(); SaveWebServiceValues(); SaveMiscellaneousValues(); SaveLibraryAppletValues(); } else { LOG_DEBUG(Config, "Saving only generic configuration values"); } + SaveNetworkValues(); SaveControlValues(); SaveCoreValues(); SaveCpuValues(); diff --git a/src/frontend_common/play_time_manager.cpp b/src/frontend_common/play_time_manager.cpp index 4b0ba420ed..58b320198f 100644 --- a/src/frontend_common/play_time_manager.cpp +++ b/src/frontend_common/play_time_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -96,8 +96,7 @@ std::optional GetCurrentUserPlayTimePath() { } // namespace -PlayTimeManager::PlayTimeManager() - : running_program_id() { +PlayTimeManager::PlayTimeManager() : running_program_id() { if (!ReadPlayTimeFile(database)) { LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default."); } diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index b527668052..ddee11035c 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -493,10 +493,8 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en // their desktop environment. SDL_SetHint(SDL_HINT_APP_NAME, "Eden"); - if (!Settings::values.enable_raw_input) { - // Disable raw input. When enabled this setting causes SDL to die when a web applet opens - SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); - } + // Disable raw input. When enabled this setting causes SDL to die when a web applet opens + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0"); // Prevent SDL from adding undesired axis SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index a3a720db8e..163eb57138 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -163,10 +163,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) vulkan_device, tr("Device:"), tr("This setting selects the GPU to use (Vulkan only).")); - INSERT(Settings, - shader_backend, - tr("Shader Backend:"), - tr("The shader backend to use with OpenGL.\nGLSL is recommended.")); INSERT(Settings, resolution_setup, tr("Resolution:"), @@ -288,6 +284,22 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Fast GPU Time"), tr("Overclocks the emulated GPU to increase dynamic resolution and render " "distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity.")); + INSERT(Settings, + gpu_unswizzle_texture_size, + tr("GPU Unswizzle Max Texture Size"), + tr("Sets the maximum size (MiB) for GPU-based texture unswizzling.\n" + "While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones.\n" + "Adjust this to find the balance between GPU acceleration and CPU overhead.")); + INSERT(Settings, + gpu_unswizzle_stream_size, + tr("GPU Unswizzle Stream Size"), + tr("Sets the maximum amount of texture data (in MiB) processed per frame.\n" + "Higher values can reduce stutter during texture loading but may impact frame consistency.")); + INSERT(Settings, + gpu_unswizzle_chunk_size, + tr("GPU Unswizzle Chunk Size"), + tr("Determines the number of depth slices processed in a single dispatch.\n" + "Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware.")); INSERT(Settings, use_vulkan_driver_pipeline_cache, @@ -317,6 +329,10 @@ std::unique_ptr InitializeTranslations(QObject* parent) barrier_feedback_loops, tr("Barrier feedback loops"), tr("Improves rendering of transparency effects in specific games.")); + INSERT(Settings, + fix_bloom_effects, + tr("Fix bloom effects"), + tr("Removes bloom in Burnout.")); // Renderer (Extensions) INSERT(Settings, dyna_state, tr("Extended Dynamic State"), @@ -482,21 +498,15 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(VramUsageMode, Conservative, tr("Conservative")), PAIR(VramUsageMode, Aggressive, tr("Aggressive")), }}); - translations->insert({Settings::EnumMetadata::Index(), - { + translations->insert({Settings::EnumMetadata::Index(), { + PAIR(RendererBackend, Vulkan, tr("Vulkan")), #ifdef HAS_OPENGL - PAIR(RendererBackend, OpenGL, tr("OpenGL")), + PAIR(RendererBackend, OpenGL_GLSL, tr("OpenGL GLSL")), + PAIR(RendererBackend, OpenGL_GLASM, tr("OpenGL GLASM (Assembly Shaders, NVIDIA Only)")), + PAIR(RendererBackend, OpenGL_SPIRV, tr("OpenGL SPIR-V (Experimental, AMD/Mesa Only)")), #endif - PAIR(RendererBackend, Vulkan, tr("Vulkan")), - PAIR(RendererBackend, Null, tr("Null")), - }}); - translations->insert( - {Settings::EnumMetadata::Index(), - { - PAIR(ShaderBackend, Glsl, tr("GLSL")), - PAIR(ShaderBackend, Glasm, tr("GLASM (Assembly Shaders, NVIDIA Only)")), - PAIR(ShaderBackend, SpirV, tr("SPIR-V (Experimental, AMD/Mesa Only)")), - }}); + PAIR(RendererBackend, Null, tr("Null")) + }}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(GpuAccuracy, Low, tr("Fast")), @@ -719,6 +729,30 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(GpuOverclock, Medium, tr("Medium (256)")), PAIR(GpuOverclock, High, tr("High (512)")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuUnswizzleSize, VerySmall, tr("Very Small (16 MB)")), + PAIR(GpuUnswizzleSize, Small, tr("Small (32 MB)")), + PAIR(GpuUnswizzleSize, Normal, tr("Normal (128 MB)")), + PAIR(GpuUnswizzleSize, Large, tr("Large (256 MB)")), + PAIR(GpuUnswizzleSize, VeryLarge, tr("Very Large (512 MB)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuUnswizzle, VeryLow, tr("Very Low (4 MB)")), + PAIR(GpuUnswizzle, Low, tr("Low (8 MB)")), + PAIR(GpuUnswizzle, Normal, tr("Normal (16 MB)")), + PAIR(GpuUnswizzle, Medium, tr("Medium (32 MB)")), + PAIR(GpuUnswizzle, High, tr("High (64 MB)")), + }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuUnswizzleChunk, VeryLow, tr("Very Low (32)")), + PAIR(GpuUnswizzleChunk, Low, tr("Low (64)")), + PAIR(GpuUnswizzleChunk, Normal, tr("Normal (128)")), + PAIR(GpuUnswizzleChunk, Medium, tr("Medium (256)")), + PAIR(GpuUnswizzleChunk, High, tr("High (512)")), + }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h index e12b93ea6d..afb18ec435 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -68,14 +68,10 @@ static const std::map gpu_accuracy_texts_map = { static const std::map renderer_backend_texts_map = { {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Vulkan"))}, - {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL"))}, + {Settings::RendererBackend::OpenGL_GLSL, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLSL"))}, + {Settings::RendererBackend::OpenGL_SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL SPIRV"))}, + {Settings::RendererBackend::OpenGL_GLASM, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLASM"))}, {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Null"))}, }; -static const std::map shader_backend_texts_map = { - {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "GLSL"))}, - {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "GLASM"))}, - {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SPIRV"))}, -}; - } // namespace ConfigurationShared diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index d139f43871..679d00782d 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -294,7 +294,6 @@ Q_DECLARE_METATYPE(Settings::ResolutionSetup); Q_DECLARE_METATYPE(Settings::ScalingFilter); Q_DECLARE_METATYPE(Settings::AntiAliasing); Q_DECLARE_METATYPE(Settings::RendererBackend); -Q_DECLARE_METATYPE(Settings::ShaderBackend); Q_DECLARE_METATYPE(Settings::AstcRecompression); Q_DECLARE_METATYPE(Settings::AstcDecodeMode); Q_DECLARE_METATYPE(Settings::SpirvOptimizeMode); diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp index 2bf7f4de13..a2df6159fb 100644 --- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp +++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -298,10 +298,12 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile stage_name = "gs"; header += fmt::format("layout({})in;", InputPrimitive(runtime_info.input_topology)); if (uses_geometry_passthrough) { - header += "layout(passthrough)in gl_PerVertex{vec4 gl_Position;};"; + // Passthru REQUIRES the layout to be defined with a corresponding name, for our sanity + // we will just use `gl_in[]`, if you don't the driver will complain with: + // 0(56) : error C7593: Builtin block member gl_Position not found in redeclaration of in gl_PerVertex + header += "layout(passthrough)in gl_PerVertex{vec4 gl_Position;}gl_in[];"; break; - } else if (program.is_geometry_passthrough && - !profile.support_geometry_shader_passthrough) { + } else if (program.is_geometry_passthrough && !profile.support_geometry_shader_passthrough) { LOG_WARNING(Shader_GLSL, "Passthrough geometry program used but not supported"); } header += fmt::format( diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 945cdb42bc..9010e8e3e3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/settings.h" #include "shader_recompiler/backend/spirv/emit_spirv.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" @@ -471,9 +475,16 @@ Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& Id lod, const IR::Value& offset) { const auto info{inst->Flags()}; const ImageOperands operands(ctx, false, true, false, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + + Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); +#ifdef ANDROID + if (Settings::values.fix_bloom_effects.GetValue()) { + result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f)); + } +#endif + return result; } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index dd4a9e2d03..38d1e075a1 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 66cdb1d3db..80ac79f2d6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h index 5dbbc7e61e..106fe8d32d 100644 --- a/src/shader_recompiler/environment.h +++ b/src/shader_recompiler/environment.h @@ -1,17 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include +#include #include "common/common_types.h" #include "shader_recompiler/program_header.h" #include "shader_recompiler/shader_info.h" #include "shader_recompiler/stage.h" +#include "shader_recompiler/frontend/ir/value.h" + +namespace Shader::IR { +class Inst; +} namespace Shader { +struct CbufWordKey { + u32 index; + u32 offset; + constexpr bool operator==(const CbufWordKey& o) const noexcept { + return index == o.index && offset == o.offset; + } +}; + +struct CbufWordKeyHash { + constexpr size_t operator()(const CbufWordKey& k) const noexcept { + return (size_t(k.index) << 32) ^ k.offset; + } +}; + +struct HandleKey { + u32 index, offset, shift_left; + u32 sec_index, sec_offset, sec_shift_left; + bool has_secondary; + constexpr bool operator==(const HandleKey& o) const noexcept { + return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, has_secondary) + == std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.has_secondary); + } +}; +struct HandleKeyHash { + constexpr size_t operator()(const HandleKey& k) const noexcept { + size_t h = (size_t(k.index) << 32) ^ k.offset; + h ^= (size_t(k.shift_left) << 1); + h ^= (size_t(k.sec_index) << 33) ^ (size_t(k.sec_offset) << 2); + h ^= (size_t(k.sec_shift_left) << 3); + h ^= k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL; + return h; + } +}; + +struct ConstBufferAddr { + u32 index; + u32 offset; + u32 shift_left; + u32 secondary_index; + u32 secondary_offset; + u32 secondary_shift_left; + IR::U32 dynamic_offset; + u32 count; + bool has_secondary; +}; + class Environment { public: virtual ~Environment() = default; @@ -69,6 +124,10 @@ protected: Stage stage{}; u32 start_address{}; bool is_proprietary_driver{}; +public: + std::unordered_map cbuf_word_cache; + std::unordered_map handle_cache; + std::unordered_map track_cache; }; } // namespace Shader diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index 97b9b0cf07..dfd5f405fb 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -304,9 +304,17 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool +#include #include "shader_recompiler/frontend/ir/basic_block.h" #include "shader_recompiler/frontend/ir/value.h" @@ -10,28 +14,30 @@ namespace Shader::Optimization { void IdentityRemovalPass(IR::Program& program) { - std::vector to_invalidate; + boost::container::small_vector to_invalidate; for (IR::Block* const block : program.blocks) { - for (auto inst = block->begin(); inst != block->end();) { - const size_t num_args{inst->NumArgs()}; + for (auto it = block->begin(); it != block->end();) { + const size_t num_args{it->NumArgs()}; for (size_t i = 0; i < num_args; ++i) { - IR::Value arg; - while ((arg = inst->Arg(i)).IsIdentity()) { - inst->SetArg(i, arg.Inst()->Arg(0)); + IR::Value arg = it->Arg(i); + if (arg.IsIdentity()) { + do { // Pointer chasing (3-derefs) + arg = arg.Inst()->Arg(0); + } while (arg.IsIdentity()); + it->SetArg(i, arg); } } - if (inst->GetOpcode() == IR::Opcode::Identity || - inst->GetOpcode() == IR::Opcode::Void) { - to_invalidate.push_back(&*inst); - inst = block->Instructions().erase(inst); + + if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) { + to_invalidate.push_back(&*it); + it = block->Instructions().erase(it); } else { - ++inst; + ++it; } } } - for (IR::Inst* const inst : to_invalidate) { + for (IR::Inst* const inst : to_invalidate) inst->Invalidate(); - } } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 9f04c0afaf..0288db0ae1 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,7 +7,9 @@ #include #include #include - +#include +#include +#include #include #include "shader_recompiler/environment.h" @@ -20,18 +22,6 @@ namespace Shader::Optimization { namespace { -struct ConstBufferAddr { - u32 index; - u32 offset; - u32 shift_left; - u32 secondary_index; - u32 secondary_offset; - u32 secondary_shift_left; - IR::U32 dynamic_offset; - u32 count; - bool has_secondary; -}; - struct TextureInst { ConstBufferAddr cbuf; IR::Inst* inst; @@ -177,12 +167,59 @@ bool IsBindless(const IR::Inst& inst) { bool IsTextureInstruction(const IR::Inst& inst) { return IndexedInstruction(inst) != IR::Opcode::Void; } +// Per-pass caches + +static inline u32 ReadCbufCached(Environment& env, u32 index, u32 offset) { + const CbufWordKey k{index, offset}; + if (auto it = env.cbuf_word_cache.find(k); it != env.cbuf_word_cache.end()) return it->second; + const u32 v = env.ReadCbufValue(index, offset); + env.cbuf_word_cache.emplace(k, v); + return v; +} + +static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) { + const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index; + const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset; + const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, + sec_idx, sec_off, cbuf.secondary_shift_left, cbuf.has_secondary}; + if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end()) return it->second; + + const u32 lhs = ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left; + const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left; + const u32 handle = lhs | rhs; + env.handle_cache.emplace(hk, handle); + return handle; +} + +// Cached variants of existing helpers +static inline TextureType ReadTextureTypeCached(Environment& env, const ConstBufferAddr& cbuf) { + return env.ReadTextureType(GetTextureHandleCached(env, cbuf)); +} +static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env, + const ConstBufferAddr& cbuf) { + return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf)); +} +static inline bool IsTexturePixelFormatIntegerCached(Environment& env, + const ConstBufferAddr& cbuf) { + return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf)); +} + + +std::optional Track(const IR::Value& value, Environment& env); +static inline std::optional TrackCached(const IR::Value& v, Environment& env) { + if (const IR::Inst* key = v.InstRecursive()) { + if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second; + auto found = Track(v, env); + if (found) env.track_cache.emplace(key, *found); + return found; + } + return Track(v, env); +} std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env); std::optional Track(const IR::Value& value, Environment& env) { - return IR::BreadthFirstSearch( - value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); }); + return IR::BreadthFirstSearch(value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); }); } std::optional TryGetConstant(IR::Value& value, Environment& env) { @@ -203,7 +240,7 @@ std::optional TryGetConstant(IR::Value& value, Environment& env) { return std::nullopt; } const auto offset_number = offset.U32(); - return env.ReadCbufValue(index_number, offset_number); + return ReadCbufCached(env, index_number, offset_number); } std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env) { @@ -211,8 +248,8 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme default: return std::nullopt; case IR::Opcode::BitwiseOr32: { - std::optional lhs{Track(inst->Arg(0), env)}; - std::optional rhs{Track(inst->Arg(1), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env)}; + std::optional rhs{TrackCached(inst->Arg(1), env)}; if (!lhs || !rhs) { return std::nullopt; } @@ -242,7 +279,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme if (!shift.IsImmediate()) { return std::nullopt; } - std::optional lhs{Track(inst->Arg(0), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env)}; if (lhs) { lhs->shift_left = shift.U32(); } @@ -271,7 +308,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme return std::nullopt; } while (false); } - std::optional lhs{Track(op1, env)}; + std::optional lhs{TrackCached(op1, env)}; if (lhs) { lhs->shift_left = static_cast(std::countr_zero(op2.U32())); } @@ -330,30 +367,15 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme }; } -// TODO:xbzk: shall be dropped when Track method cover all bindless stuff -static ConstBufferAddr last_valid_addr = ConstBufferAddr{ - .index = 0, - .offset = 0, - .shift_left = 0, - .secondary_index = 0, - .secondary_offset = 0, - .secondary_shift_left = 0, - .dynamic_offset = {}, - .count = 1, - .has_secondary = false, -}; - TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { ConstBufferAddr addr; if (IsBindless(inst)) { - const std::optional track_addr{Track(inst.Arg(0), env)}; + const std::optional track_addr{TrackCached(inst.Arg(0), env)}; if (!track_addr) { - //throw NotImplementedException("Failed to track bindless texture constant buffer"); - addr = last_valid_addr; // TODO:xbzk: shall be dropped when Track method cover all bindless stuff + throw NotImplementedException("Failed to track bindless texture constant buffer"); } else { addr = *track_addr; - last_valid_addr = addr; // TODO:xbzk: shall be dropped when Track method cover all bindless stuff } } else { addr = ConstBufferAddr{ @@ -384,15 +406,15 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) { return lhs_raw | rhs_raw; } -TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTextureType(GetTextureHandle(env, cbuf)); } -TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf)); } -bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf)); } @@ -543,6 +565,10 @@ void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_ } // Anonymous namespace void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info) { + // reset per-pass caches + env.cbuf_word_cache.clear(); + env.handle_cache.clear(); + env.track_cache.clear(); TextureInstVector to_replace; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { @@ -553,11 +579,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } } // Sort instructions to visit textures by constant buffer index, then by offset - std::ranges::sort(to_replace, [](const auto& lhs, const auto& rhs) { - return lhs.cbuf.offset < rhs.cbuf.offset; - }); - std::stable_sort(to_replace.begin(), to_replace.end(), [](const auto& lhs, const auto& rhs) { - return lhs.cbuf.index < rhs.cbuf.index; + std::ranges::sort(to_replace, [](const auto& a, const auto& b) { + if (a.cbuf.index != b.cbuf.index) return a.cbuf.index < b.cbuf.index; + return a.cbuf.offset < b.cbuf.offset; }); Descriptors descriptors{ program.info.texture_buffer_descriptors, @@ -575,14 +599,14 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo bool is_multisample{false}; switch (inst->GetOpcode()) { case IR::Opcode::ImageQueryDimensions: - flags.type.Assign(ReadTextureType(env, cbuf)); + flags.type.Assign(ReadTextureTypeCached(env, cbuf)); inst->SetFlags(flags); break; case IR::Opcode::ImageSampleImplicitLod: if (flags.type != TextureType::Color2D) { break; } - if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) { + if (ReadTextureTypeCached(env, cbuf) == TextureType::Color2DRect) { PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); } break; @@ -596,7 +620,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo if (flags.type != TextureType::Color1D) { break; } - if (ReadTextureType(env, cbuf) == TextureType::Buffer) { + if (ReadTextureTypeCached(env, cbuf) == TextureType::Buffer) { // Replace with the bound texture type only when it's a texture buffer // If the instruction is 1D and the bound type is 2D, don't change the code and let // the rasterizer robustness handle it @@ -627,7 +651,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead}; const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite}; - const bool is_integer{IsTexturePixelFormatInteger(env, cbuf)}; + const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -691,16 +715,16 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo if (cbuf.count > 1) { const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)}; IR::IREmitter ir{*texture_inst.block, insert_point}; - const IR::U32 shift{ir.Imm32(std::countr_zero(DESCRIPTOR_SIZE))}; - inst->SetArg(0, ir.UMin(ir.ShiftRightArithmetic(cbuf.dynamic_offset, shift), - ir.Imm32(DESCRIPTOR_SIZE - 1))); + const IR::U32 shift{ir.Imm32(DESCRIPTOR_SIZE_SHIFT)}; + inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), + ir.Imm32(DESCRIPTOR_SIZE - 1))); } else { inst->SetArg(0, IR::Value{}); } if (!host_info.support_snorm_render_buffer && inst->GetOpcode() == IR::Opcode::ImageFetch && flags.type == TextureType::Buffer) { - const auto pixel_format = ReadTexturePixelFormat(env, cbuf); + const auto pixel_format = ReadTexturePixelFormatCached(env, cbuf); if (IsPixelFormatSNorm(pixel_format)) { PatchTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format); } diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index ed13e68209..dfacc06802 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 236010ed38..43745af429 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(tests common/range_map.cpp common/ring_buffer.cpp common/scratch_buffer.cpp + common/undefined_fix.cpp common/unique_function.cpp core/core_timing.cpp core/internal_network/network.cpp diff --git a/src/tests/common/undefined_fix.cpp b/src/tests/common/undefined_fix.cpp new file mode 100644 index 0000000000..ba83fd519f --- /dev/null +++ b/src/tests/common/undefined_fix.cpp @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "frontend_common/play_time_manager.h" + +namespace PlayTime { + +PlayTimeManager::PlayTimeManager() {} +PlayTimeManager::~PlayTimeManager() {} +u64 PlayTimeManager::GetPlayTime(u64 program_id) const { + return 0; +} + +} // namespace PlayTime diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index b6fdefe0fc..08ebf05237 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -1,14 +1,20 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include #include #include +#include +#include +#include #include #include "common/common_types.h" #include "video_core/buffer_cache/memory_tracker_base.h" +#include "core/device_memory.h" +#include "core/memory.h" +#include "video_core/host1x/gpu_device_memory_manager.h" namespace { using Range = std::pair; @@ -18,11 +24,13 @@ constexpr u64 WORD = 4096 * 64; constexpr u64 HIGH_PAGE_BITS = 22; constexpr u64 HIGH_PAGE_SIZE = 1ULL << HIGH_PAGE_BITS; -constexpr VAddr c = 16 * HIGH_PAGE_SIZE; +constexpr DAddr c = 16 * HIGH_PAGE_SIZE; class RasterizerInterface { public: - void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { + void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) { + ++update_calls; + calls.emplace_back(addr, size, delta); const u64 page_start{addr >> Core::DEVICE_PAGEBITS}; const u64 page_end{(addr + size + Core::DEVICE_PAGESIZE - 1) >> Core::DEVICE_PAGEBITS}; for (u64 page = page_start; page < page_end; ++page) { @@ -36,7 +44,14 @@ public: } } - [[nodiscard]] int Count(VAddr addr) const noexcept { + void UpdatePagesCachedBatch(std::span> ranges, s32 delta) { + // TODO: for now assume fine? + } + + [[nodiscard]] size_t UpdateCalls() const noexcept { return update_calls; } + [[nodiscard]] const std::vector>& UpdateCallsList() const noexcept { return calls; } + + [[nodiscard]] int Count(DAddr addr) const noexcept { const auto it = page_table.find(addr >> Core::DEVICE_PAGEBITS); return it == page_table.end() ? 0 : it->second; } @@ -51,14 +66,17 @@ public: private: std::unordered_map page_table; + std::vector> calls; + size_t update_calls = 0; }; + } // Anonymous namespace using MemoryTracker = VideoCommon::MemoryTrackerBase; TEST_CASE("MemoryTracker: Small region", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE(rasterizer.Count() == 0); memory_track->UnmarkRegionAsCpuModified(c, WORD); REQUIRE(rasterizer.Count() == WORD / PAGE); @@ -70,27 +88,21 @@ TEST_CASE("MemoryTracker: Small region", "[video_core]") { TEST_CASE("MemoryTracker: Large region", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 32); memory_track->MarkRegionAsCpuModified(c + 4096, WORD * 4); - REQUIRE(memory_track->ModifiedCpuRegion(c, WORD + PAGE * 2) == - Range{c + PAGE, c + WORD + PAGE * 2}); - REQUIRE(memory_track->ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == - Range{c + PAGE * 2, c + PAGE * 8}); + REQUIRE(memory_track->ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{c + PAGE, c + WORD + PAGE * 2}); + REQUIRE(memory_track->ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{c + PAGE * 2, c + PAGE * 8}); REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 4 + PAGE}); - REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 4, PAGE) == - Range{c + WORD * 4, c + WORD * 4 + PAGE}); - REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) == - Range{c + WORD * 3 + PAGE * 63, c + WORD * 4}); + REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{c + WORD * 4, c + WORD * 4 + PAGE}); + REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) == Range{c + WORD * 3 + PAGE * 63, c + WORD * 4}); memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE); memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); - REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) == - Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 9}); + REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) == Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 9}); memory_track->UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); - REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) == - Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 7}); + REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) == Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 7}); memory_track->MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63); REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 32}); @@ -104,7 +116,7 @@ TEST_CASE("MemoryTracker: Large region", "[video_core]") { TEST_CASE("MemoryTracker: Rasterizer counting", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE(rasterizer.Count() == 0); memory_track->UnmarkRegionAsCpuModified(c, PAGE); REQUIRE(rasterizer.Count() == 1); @@ -119,7 +131,7 @@ TEST_CASE("MemoryTracker: Rasterizer counting", "[video_core]") { TEST_CASE("MemoryTracker: Basic range", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->MarkRegionAsCpuModified(c, PAGE); int num = 0; @@ -133,7 +145,7 @@ TEST_CASE("MemoryTracker: Basic range", "[video_core]") { TEST_CASE("MemoryTracker: Border upload", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 2); memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); memory_track->ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) { @@ -144,7 +156,7 @@ TEST_CASE("MemoryTracker: Border upload", "[video_core]") { TEST_CASE("MemoryTracker: Border upload range", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 2); memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) { @@ -164,7 +176,7 @@ TEST_CASE("MemoryTracker: Border upload range", "[video_core]") { TEST_CASE("MemoryTracker: Border upload partial range", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 2); memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); memory_track->ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) { @@ -184,7 +196,7 @@ TEST_CASE("MemoryTracker: Border upload partial range", "[video_core]") { TEST_CASE("MemoryTracker: Partial word uploads", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); int num = 0; memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { REQUIRE(offset == c); @@ -208,7 +220,7 @@ TEST_CASE("MemoryTracker: Partial word uploads", "[video_core]") { TEST_CASE("MemoryTracker: Partial page upload", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); int num = 0; memory_track->MarkRegionAsCpuModified(c + PAGE * 2, PAGE); @@ -229,7 +241,7 @@ TEST_CASE("MemoryTracker: Partial page upload", "[video_core]") { TEST_CASE("MemoryTracker: Partial page upload with multiple words on the right") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 9); memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); int num = 0; @@ -249,7 +261,7 @@ TEST_CASE("MemoryTracker: Partial page upload with multiple words on the right") TEST_CASE("MemoryTracker: Partial page upload with multiple words on the left", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 8); memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); int num = 0; @@ -269,7 +281,7 @@ TEST_CASE("MemoryTracker: Partial page upload with multiple words on the left", TEST_CASE("MemoryTracker: Partial page upload with multiple words in the middle", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 8); memory_track->MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140); int num = 0; @@ -295,7 +307,7 @@ TEST_CASE("MemoryTracker: Partial page upload with multiple words in the middle" TEST_CASE("MemoryTracker: Empty right bits", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 2048); memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); memory_track->ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) { @@ -306,7 +318,7 @@ TEST_CASE("MemoryTracker: Empty right bits", "[video_core]") { TEST_CASE("MemoryTracker: Out of bound ranges 1", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c - WORD, 3 * WORD); memory_track->MarkRegionAsCpuModified(c, PAGE); REQUIRE(rasterizer.Count() == (3 * WORD - PAGE) / PAGE); @@ -323,7 +335,7 @@ TEST_CASE("MemoryTracker: Out of bound ranges 1", "[video_core]") { TEST_CASE("MemoryTracker: Out of bound ranges 2", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x22000, PAGE)); REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x28000, PAGE)); REQUIRE(rasterizer.Count() == 2); @@ -337,7 +349,7 @@ TEST_CASE("MemoryTracker: Out of bound ranges 2", "[video_core]") { TEST_CASE("MemoryTracker: Out of bound ranges 3", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, 0x310720); REQUIRE(rasterizer.Count(c) == 1); REQUIRE(rasterizer.Count(c + PAGE) == 1); @@ -347,7 +359,7 @@ TEST_CASE("MemoryTracker: Out of bound ranges 3", "[video_core]") { TEST_CASE("MemoryTracker: Sparse regions 1", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->MarkRegionAsCpuModified(c + PAGE * 1, PAGE); memory_track->MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4); @@ -362,7 +374,7 @@ TEST_CASE("MemoryTracker: Sparse regions 1", "[video_core]") { TEST_CASE("MemoryTracker: Sparse regions 2", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, PAGE * 0x23); REQUIRE(rasterizer.Count() == 0x23); memory_track->MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE); @@ -378,7 +390,7 @@ TEST_CASE("MemoryTracker: Sparse regions 2", "[video_core]") { TEST_CASE("MemoryTracker: Single page modified range", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE(memory_track->IsRegionCpuModified(c, PAGE)); memory_track->UnmarkRegionAsCpuModified(c, PAGE); REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE)); @@ -386,7 +398,7 @@ TEST_CASE("MemoryTracker: Single page modified range", "[video_core]") { TEST_CASE("MemoryTracker: Two page modified range", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE(memory_track->IsRegionCpuModified(c, PAGE)); REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE)); REQUIRE(memory_track->IsRegionCpuModified(c, PAGE * 2)); @@ -396,9 +408,9 @@ TEST_CASE("MemoryTracker: Two page modified range", "[video_core]") { TEST_CASE("MemoryTracker: Multi word modified ranges", "[video_core]") { for (int offset = 0; offset < 4; ++offset) { - const VAddr address = c + WORD * offset; + const DAddr address = c + WORD * offset; RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); REQUIRE(memory_track->IsRegionCpuModified(address, PAGE)); REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 48, PAGE)); REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 56, PAGE)); @@ -418,7 +430,7 @@ TEST_CASE("MemoryTracker: Multi word modified ranges", "[video_core]") { TEST_CASE("MemoryTracker: Single page in large region", "[video_core]") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 16); REQUIRE(!memory_track->IsRegionCpuModified(c, WORD * 16)); @@ -436,7 +448,7 @@ TEST_CASE("MemoryTracker: Single page in large region", "[video_core]") { TEST_CASE("MemoryTracker: Wrap word regions") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD * 32); memory_track->MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2); REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 2)); @@ -458,7 +470,7 @@ TEST_CASE("MemoryTracker: Wrap word regions") { TEST_CASE("MemoryTracker: Unaligned page region query") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->MarkRegionAsCpuModified(c + 4000, 1000); REQUIRE(memory_track->IsRegionCpuModified(c, PAGE)); @@ -469,7 +481,7 @@ TEST_CASE("MemoryTracker: Unaligned page region query") { TEST_CASE("MemoryTracker: Cached write") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->CachedCpuWrite(c + PAGE, c + PAGE); REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE)); @@ -481,7 +493,7 @@ TEST_CASE("MemoryTracker: Cached write") { TEST_CASE("MemoryTracker: Multiple cached write") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->CachedCpuWrite(c + PAGE, PAGE); memory_track->CachedCpuWrite(c + PAGE * 3, PAGE); @@ -496,7 +508,7 @@ TEST_CASE("MemoryTracker: Multiple cached write") { TEST_CASE("MemoryTracker: Cached write unmarked") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->CachedCpuWrite(c + PAGE, PAGE); memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE); @@ -509,7 +521,7 @@ TEST_CASE("MemoryTracker: Cached write unmarked") { TEST_CASE("MemoryTracker: Cached write iterated") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); memory_track->CachedCpuWrite(c + PAGE, PAGE); int num = 0; @@ -524,7 +536,7 @@ TEST_CASE("MemoryTracker: Cached write iterated") { TEST_CASE("MemoryTracker: Cached write downloads") { RasterizerInterface rasterizer; - std::unique_ptr memory_track(std::make_unique(rasterizer)); + std::optional memory_track(rasterizer); memory_track->UnmarkRegionAsCpuModified(c, WORD); REQUIRE(rasterizer.Count() == 64); memory_track->CachedCpuWrite(c + PAGE, PAGE); @@ -544,3 +556,34 @@ TEST_CASE("MemoryTracker: Cached write downloads") { memory_track->MarkRegionAsCpuModified(c, WORD); REQUIRE(rasterizer.Count() == 0); } + +TEST_CASE("MemoryTracker: FlushCachedWrites batching") { + RasterizerInterface rasterizer; + std::optional memory_track(rasterizer); + memory_track->UnmarkRegionAsCpuModified(c, WORD * 2); + memory_track->CachedCpuWrite(c + PAGE, PAGE); + memory_track->CachedCpuWrite(c + PAGE * 2, PAGE); + memory_track->CachedCpuWrite(c + PAGE * 4, PAGE); + REQUIRE(rasterizer.UpdateCalls() == 0); + memory_track->FlushCachedWrites(); + // Now we expect a single batch call (coalesced ranges) to the device memory manager + REQUIRE(rasterizer.UpdateCalls() == 1); + const auto& calls = rasterizer.UpdateCallsList(); + REQUIRE(std::get<0>(calls[0]) == c + PAGE); + REQUIRE(std::get<1>(calls[0]) == PAGE * 3); +} + +TEST_CASE("DeviceMemoryManager: UpdatePagesCachedBatch basic") { + Core::DeviceMemory device_memory; + Tegra::MaxwellDeviceMemoryManager manager(device_memory); + // empty should be a no-op + std::vector> empty; + manager.UpdatePagesCachedBatch(empty, 1); + + // small ranges should be accepted and not crash + std::vector> ranges; + ranges.emplace_back(0, Core::Memory::YUZU_PAGESIZE); + ranges.emplace_back(Core::Memory::YUZU_PAGESIZE, Core::Memory::YUZU_PAGESIZE); + manager.UpdatePagesCachedBatch(ranges, 1); + SUCCEED("UpdatePagesCachedBatch executed without error"); +} diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 538622716a..26c7139ba5 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -88,12 +88,8 @@ add_library(video_core STATIC host1x/syncpoint_manager.h host1x/vic.cpp host1x/vic.h - macro/macro.cpp - macro/macro.h - macro/macro_hle.cpp - macro/macro_hle.h - macro/macro_interpreter.cpp - macro/macro_interpreter.h + macro.cpp + macro.h fence_manager.h gpu.cpp gpu.h @@ -376,10 +372,6 @@ else() endif() if (ARCHITECTURE_x86_64) - target_sources(video_core PRIVATE - macro/macro_jit_x64.cpp - macro/macro_jit_x64.h - ) target_link_libraries(video_core PUBLIC xbyak::xbyak) endif() diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index b93bd57089..51f38a2eb9 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "common/alignment.h" #include "common/common_funcs.h" @@ -256,9 +257,10 @@ public: std::span state_words = words.template Span(); [[maybe_unused]] std::span untracked_words = words.template Span(); [[maybe_unused]] std::span cached_words = words.template Span(); + std::vector> ranges; IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { if constexpr (type == Type::CPU || type == Type::CachedCPU) { - NotifyRasterizer(index, untracked_words[index], mask); + CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges); } if constexpr (enable) { state_words[index] |= mask; @@ -279,6 +281,9 @@ public: } } }); + if (!ranges.empty()) { + ApplyCollectedRanges(ranges, (!enable) ? 1 : -1); + } } /** @@ -304,6 +309,7 @@ public: func(cpu_addr + pending_offset * BYTES_PER_PAGE, (pending_pointer - pending_offset) * BYTES_PER_PAGE); }; + std::vector> ranges; IterateWords(offset, size, [&](size_t index, u64 mask) { if constexpr (type == Type::GPU) { mask &= ~untracked_words[index]; @@ -311,7 +317,7 @@ public: const u64 word = state_words[index] & mask; if constexpr (clear) { if constexpr (type == Type::CPU || type == Type::CachedCPU) { - NotifyRasterizer(index, untracked_words[index], mask); + CollectChangedRanges(index, untracked_words[index], mask, ranges); } state_words[index] &= ~mask; if constexpr (type == Type::CPU || type == Type::CachedCPU) { @@ -343,6 +349,9 @@ public: if (pending) { release(); } + if (!ranges.empty()) { + ApplyCollectedRanges(ranges, 1); + } } /** @@ -425,13 +434,17 @@ public: u64* const cached_words = Array(); u64* const untracked_words = Array(); u64* const cpu_words = Array(); + std::vector> ranges; for (u64 word_index = 0; word_index < num_words; ++word_index) { const u64 cached_bits = cached_words[word_index]; - NotifyRasterizer(word_index, untracked_words[word_index], cached_bits); + CollectChangedRanges(word_index, untracked_words[word_index], cached_bits, ranges); untracked_words[word_index] |= cached_bits; cpu_words[word_index] |= cached_bits; cached_words[word_index] = 0; } + if (!ranges.empty()) { + ApplyCollectedRanges(ranges, -1); + } } private: @@ -470,6 +483,40 @@ private: * * @tparam add_to_tracker True when the tracker should start tracking the new pages */ + template + void CollectChangedRanges(u64 word_index, u64 current_bits, u64 new_bits, + std::vector>& out_ranges) const { + u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; + VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; + IteratePages(changed_bits, [&](size_t offset, size_t size) { + out_ranges.emplace_back(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE); + }); + } + + void ApplyCollectedRanges(std::vector>& ranges, int delta) const { + if (ranges.empty()) return; + std::sort(ranges.begin(), ranges.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + // Coalesce adjacent/contiguous ranges + std::vector> coalesced; + coalesced.reserve(ranges.size()); + VAddr cur_addr = ranges[0].first; + size_t cur_size = static_cast(ranges[0].second); + for (size_t i = 1; i < ranges.size(); ++i) { + if (cur_addr + cur_size == ranges[i].first) { + cur_size += static_cast(ranges[i].second); + } else { + coalesced.emplace_back(cur_addr, cur_size); + cur_addr = ranges[i].first; + cur_size = static_cast(ranges[i].second); + } + } + coalesced.emplace_back(cur_addr, cur_size); + // Use batch API to reduce lock acquisitions and contention. + tracker->UpdatePagesCachedBatch(coalesced, delta); + ranges.clear(); + } + template void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const { u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; diff --git a/src/video_core/control/scheduler.cpp b/src/video_core/control/scheduler.cpp index 441466beb2..bd3b8b860e 100644 --- a/src/video_core/control/scheduler.cpp +++ b/src/video_core/control/scheduler.cpp @@ -17,11 +17,16 @@ Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {} Scheduler::~Scheduler() = default; void Scheduler::Push(s32 channel, CommandList&& entries) { - std::unique_lock lk(scheduling_guard); - auto it = channels.find(channel); - ASSERT(it != channels.end()); - auto& channel_state = it->second; - gpu.BindChannel(channel_state->bind_id); + std::shared_ptr channel_state; + { + std::unique_lock lk(scheduling_guard); + auto it = channels.find(channel); + ASSERT(it != channels.end()); + channel_state = it->second; + gpu.BindChannel(channel_state->bind_id); + } + // Process commands outside the lock to reduce contention. + // Multiple channels can prepare their commands in parallel. channel_state->dma_pusher->Push(std::move(entries)); channel_state->dma_pusher->DispatchCalls(); } diff --git a/src/video_core/dirty_flags.h b/src/video_core/dirty_flags.h index 736082d837..bf90db83e6 100644 --- a/src/video_core/dirty_flags.h +++ b/src/video_core/dirty_flags.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -45,6 +48,71 @@ enum : u8 { LastCommonEntry, }; +constexpr std::pair GetDirtyFlagsForMethod(u32 method) { + const u32 OFF_VERTEX_STREAMS = 0x2C0; + const u32 OFF_VERTEX_STREAM_LIMITS = 0x2F8; + const u32 OFF_INDEX_BUFFER = 0x460; + const u32 OFF_TEX_HEADER = 0x800; + const u32 OFF_TEX_SAMPLER = 0xA00; + const u32 OFF_RT = 0xE00; + const u32 OFF_SURFACE_CLIP = 0xE38; + const u32 OFF_RT_CONTROL = 0xE40; + const u32 OFF_ZETA_ENABLE = 0xE4C; + const u32 OFF_ZETA_SIZE_WIDTH = 0xE50; + const u32 OFF_ZETA_SIZE_HEIGHT = 0xE54; + const u32 OFF_ZETA = 0xE60; + const u32 OFF_PIPELINES = 0x1D00; + + if (method >= OFF_VERTEX_STREAMS && method < OFF_VERTEX_STREAMS + 96) { + const u32 buffer_idx = (method - OFF_VERTEX_STREAMS) / 3; + return {static_cast(VertexBuffer0 + buffer_idx), VertexBuffers}; + } + + if (method >= OFF_VERTEX_STREAM_LIMITS && method < OFF_VERTEX_STREAM_LIMITS + 32) { + const u32 buffer_idx = method - OFF_VERTEX_STREAM_LIMITS; + return {static_cast(VertexBuffer0 + buffer_idx), VertexBuffers}; + } + + if (method == OFF_INDEX_BUFFER || (method > OFF_INDEX_BUFFER && method < OFF_INDEX_BUFFER + 3)) { + return {IndexBuffer, NullEntry}; + } + + if (method >= OFF_TEX_HEADER && method < OFF_TEX_HEADER + 256) { + return {Descriptors, NullEntry}; + } + + if (method >= OFF_TEX_SAMPLER && method < OFF_TEX_SAMPLER + 256) { + return {Descriptors, NullEntry}; + } + + if (method >= OFF_RT && method < OFF_RT + 64) { + const u32 rt_idx = (method - OFF_RT) / 8; + return {static_cast(ColorBuffer0 + rt_idx), RenderTargets}; + } + + if (method == OFF_SURFACE_CLIP || (method > OFF_SURFACE_CLIP && method < OFF_SURFACE_CLIP + 4)) { + return {RenderTargets, NullEntry}; + } + + if (method == OFF_RT_CONTROL) { + return {RenderTargets, RenderTargetControl}; + } + + if (method == OFF_ZETA_ENABLE || method == OFF_ZETA_SIZE_WIDTH || method == OFF_ZETA_SIZE_HEIGHT) { + return {ZetaBuffer, RenderTargets}; + } + + if (method >= OFF_ZETA && method < OFF_ZETA + 8) { + return {ZetaBuffer, RenderTargets}; + } + + if (method >= OFF_PIPELINES && method < OFF_PIPELINES + 1024) { + return {Shaders, NullEntry}; + } + + return {NullEntry, NullEntry}; +} + template void FillBlock(Tegra::Engines::Maxwell3D::DirtyState::Table& table, std::size_t begin, std::size_t num, Integer dirty_index) { diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index a67b35453b..3844a8e2f9 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -14,6 +14,10 @@ #include "video_core/rasterizer_interface.h" #include "video_core/texture_cache/util.h" +#ifdef _MSC_VER +#include +#endif + namespace Tegra { constexpr u32 MacroRegistersStart = 0xE00; @@ -107,35 +111,27 @@ bool DmaPusher::Step() { } void DmaPusher::ProcessCommands(std::span commands) { - for (std::size_t index = 0; index < commands.size();) { - const CommandHeader& command_header = commands[index]; - - if (dma_state.method_count) { - // Data word of methods command - dma_state.dma_word_offset = static_cast(index * sizeof(u32)); - if (dma_state.non_incrementing) { - const u32 max_write = static_cast( - std::min(index + dma_state.method_count, commands.size()) - index); - CallMultiMethod(&command_header.argument, max_write); - dma_state.method_count -= max_write; - dma_state.is_last_call = true; - index += max_write; - continue; - } else { - dma_state.is_last_call = dma_state.method_count <= 1; - CallMethod(command_header.argument); - } - - if (!dma_state.non_incrementing) { - dma_state.method++; - } - - if (dma_increment_once) { - dma_state.non_incrementing = true; - } - + for (size_t index = 0; index < commands.size();) { + // Data word of methods command + if (dma_state.method_count && dma_state.non_incrementing) { + auto const& command_header = commands[index]; //must ref (MUltiMethod re) + dma_state.dma_word_offset = u32(index * sizeof(u32)); + const u32 max_write = u32(std::min(index + dma_state.method_count, commands.size()) - index); + CallMultiMethod(&command_header.argument, max_write); + dma_state.method_count -= max_write; + dma_state.is_last_call = true; + index += max_write; + } else if (dma_state.method_count) { + auto const command_header = commands[index]; //can copy + dma_state.dma_word_offset = u32(index * sizeof(u32)); + dma_state.is_last_call = dma_state.method_count <= 1; + CallMethod(command_header.argument); + dma_state.method += !dma_state.non_incrementing ? 1 : 0; + dma_state.non_incrementing |= dma_increment_once; dma_state.method_count--; + index++; } else { + auto const command_header = commands[index]; //can copy // No command active - this is the first word of a new one switch (command_header.mode) { case SubmissionMode::Increasing: @@ -151,8 +147,7 @@ void DmaPusher::ProcessCommands(std::span commands) { case SubmissionMode::Inline: dma_state.method = command_header.method; dma_state.subchannel = command_header.subchannel; - dma_state.dma_word_offset = static_cast( - -static_cast(dma_state.dma_get)); // negate to set address as 0 + dma_state.dma_word_offset = u64(-s64(dma_state.dma_get)); // negate to set address as 0 CallMethod(command_header.arg_count); dma_state.non_incrementing = true; dma_increment_once = false; @@ -165,8 +160,8 @@ void DmaPusher::ProcessCommands(std::span commands) { default: break; } + index++; } - index++; } } @@ -186,26 +181,24 @@ void DmaPusher::CallMethod(u32 argument) const { }); } else { auto subchannel = subchannels[dma_state.subchannel]; - if (!subchannel->execution_mask[dma_state.method]) [[likely]] { + if (!subchannel->execution_mask[dma_state.method]) { subchannel->method_sink.emplace_back(dma_state.method, argument); - return; + } else { + subchannel->ConsumeSink(); + subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset; + subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call); } - subchannel->ConsumeSink(); - subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset; - subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call); } } void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const { if (dma_state.method < non_puller_methods) { - puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods, - dma_state.method_count); + puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count); } else { auto subchannel = subchannels[dma_state.subchannel]; subchannel->ConsumeSink(); subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset; - subchannel->CallMultiMethod(dma_state.method, base_start, num_methods, - dma_state.method_count); + subchannel->CallMultiMethod(dma_state.method, base_start, num_methods, dma_state.method_count); } } diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 77729fd5b6..7dbb8f6617 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -27,15 +27,12 @@ constexpr u32 MacroRegistersStart = 0xE00; Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) : draw_manager{std::make_unique(this)}, system{system_}, - memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{ - memory_manager, - regs.upload} { + memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} { dirty.flags.flip(); InitializeRegisterDefaults(); execution_mask.reset(); - for (size_t i = 0; i < execution_mask.size(); i++) { - execution_mask[i] = IsMethodExecutable(static_cast(i)); - } + for (size_t i = 0; i < execution_mask.size(); i++) + execution_mask[i] = IsMethodExecutable(u32(i)); } Maxwell3D::~Maxwell3D() = default; @@ -294,43 +291,36 @@ u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { } void Maxwell3D::ConsumeSinkImpl() { - SCOPE_EXIT { - method_sink.clear(); - }; const auto control = shadow_state.shadow_ram_control; - if (control == Regs::ShadowRamControl::Track || - control == Regs::ShadowRamControl::TrackWithFilter) { - + if (control == Regs::ShadowRamControl::Track || control == Regs::ShadowRamControl::TrackWithFilter) { for (auto [method, value] : method_sink) { shadow_state.reg_array[method] = value; ProcessDirtyRegisters(method, value); } - return; - } - if (control == Regs::ShadowRamControl::Replay) { - for (auto [method, value] : method_sink) { + } else if (control == Regs::ShadowRamControl::Replay) { + for (auto [method, value] : method_sink) ProcessDirtyRegisters(method, shadow_state.reg_array[method]); - } - return; - } - for (auto [method, value] : method_sink) { - ProcessDirtyRegisters(method, value); + } else { + for (auto [method, value] : method_sink) + ProcessDirtyRegisters(method, value); } + method_sink.clear(); } void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) { - if (regs.reg_array[method] == argument) { - return; - } - regs.reg_array[method] = argument; - - for (const auto& table : dirty.tables) { - dirty.flags[table[method]] = true; + if (regs.reg_array[method] != argument) { + regs.reg_array[method] = argument; + auto const& table0 = dirty.tables[0]; + auto const& table1 = dirty.tables[1]; + u8 const flag0 = table0[method]; + u8 const flag1 = table1[method]; + dirty.flags[flag0] = true; + if (flag1 != flag0) + dirty.flags[flag1] = true; } } -void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, - bool is_last_call) { +void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call) { switch (method) { case MAXWELL3D_REG_INDEX(wait_for_idle): return rasterizer->WaitForIdle(); @@ -427,9 +417,7 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { return; } - ASSERT_MSG(method < Regs::NUM_REGS, - "Invalid Maxwell3D register, increase the size of the Regs structure"); - + ASSERT(method < Regs::NUM_REGS && "Invalid Maxwell3D register, increase the size of the Regs structure"); const u32 argument = ProcessShadowRam(method, method_argument); ProcessDirtyRegisters(method, argument); ProcessMethodCall(method, argument, method_argument, is_last_call); @@ -670,7 +658,7 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { } u32 Maxwell3D::GetRegisterValue(u32 method) const { - ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register"); + ASSERT(method < Regs::NUM_REGS && "Invalid Maxwell3D register"); return regs.reg_array[method]; } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index ae2e7a84c4..8c50a4ea2f 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -23,7 +23,7 @@ #include "video_core/engines/engine_interface.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" -#include "video_core/macro/macro.h" +#include "video_core/macro.h" #include "video_core/textures/texture.h" namespace Core { @@ -3203,7 +3203,7 @@ private: std::vector macro_params; /// Interpreter for the macro codes uploaded to the GPU. - std::unique_ptr macro_engine; + std::optional macro_engine; Upload::State upload_state; diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index f514ccfc07..94c12ff533 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -485,8 +485,8 @@ void Vic::Blend(const ConfigStruct& config, const SlotStruct& slot, VideoPixelFo source_bottom = (std::min)(source_bottom, out_surface_height); source_right = (std::min)(source_right, out_surface_width); - auto const work_width = u32((std::max)(0, s32(source_right) - s32(source_left))); - auto const work_height = u32((std::max)(0, s32(source_bottom) - s32(source_top))); + [[maybe_unused]] auto const work_width = u32((std::max)(0, s32(source_right) - s32(source_left))); + [[maybe_unused]] auto const work_height = u32((std::max)(0, s32(source_bottom) - s32(source_top))); // TODO Alpha blending. No games I've seen use more than a single surface or supply an alpha // below max, so it's ignored for now. @@ -630,42 +630,49 @@ void Vic::Blend(const ConfigStruct& config, const SlotStruct& slot, VideoPixelFo // | r1c0 r1c1 r1c2 r1c3 | * | G | = | G | // | r2c0 r2c1 r2c2 r2c3 | | B | | B | // | 1 | - auto const shift = s32(slot.color_matrix.matrix_r_shift.Value()); - - struct AliasedMatrixType { u64 m[4]; }; - static_assert(sizeof(AliasedMatrixType) == sizeof(slot.color_matrix)); - u64 const mat_mask = (1 << 20) - 1; - auto const* amt = reinterpret_cast(&slot.color_matrix); - - constexpr s32 shifts[4] = { 0, 20, 40, 60 }; - s32 mr[4][4]; - for (u32 j = 0; j < 3; ++j) - for (u32 i = 0; i < 4; ++i) - mr[j][i] = s32(s64(((amt->m[i] >> shifts[j]) & mat_mask) << (64 - 20)) >> (64 - 20)); - - auto const clamp_min = s32(slot.config.soft_clamp_low.Value()); - auto const clamp_max = s32(slot.config.soft_clamp_high.Value()); - for (u32 y = 0; y < work_height; ++y) { - auto const src = (y + source_top) * in_surface_width + source_left; - auto const dst = (y + source_top) * out_surface_width + rect_left; - for (u32 x = 0; x < work_width; ++x) { - auto const& in_pixel = slot_surface[src + x]; - auto& out_pixel = output_surface[dst + x]; - s32 const mul_values[4] = { - in_pixel.r * mr[0][0] + in_pixel.g * mr[1][1] + in_pixel.b * mr[0][2], - in_pixel.r * mr[1][0] + in_pixel.g * mr[1][1] + in_pixel.b * mr[1][2], - in_pixel.r * mr[2][0] + in_pixel.g * mr[2][1] + in_pixel.b * mr[2][2], - s32(in_pixel.a) - }; - s32 const mul_clamp[4] = { - std::clamp(((mul_values[0] >> shift) + mr[0][3]) >> 8, clamp_min, clamp_max), - std::clamp(((mul_values[1] >> shift) + mr[1][3]) >> 8, clamp_min, clamp_max), - std::clamp(((mul_values[2] >> shift) + mr[2][3]) >> 8, clamp_min, clamp_max), - std::clamp(mul_values[3], clamp_min, clamp_max) - }; - out_pixel = format == VideoPixelFormat::A8R8G8B8 - ? Pixel(u16(mul_clamp[2]), u16(mul_clamp[1]), u16(mul_clamp[0]), u16(mul_clamp[3])) - : Pixel(u16(mul_clamp[0]), u16(mul_clamp[1]), u16(mul_clamp[2]), u16(mul_clamp[3])); + const auto r0c0 = s32(slot.color_matrix.matrix_coeff00.Value()); + const auto r0c1 = s32(slot.color_matrix.matrix_coeff01.Value()); + const auto r0c2 = s32(slot.color_matrix.matrix_coeff02.Value()); + const auto r0c3 = s32(slot.color_matrix.matrix_coeff03.Value()); + const auto r1c0 = s32(slot.color_matrix.matrix_coeff10.Value()); + const auto r1c1 = s32(slot.color_matrix.matrix_coeff11.Value()); + const auto r1c2 = s32(slot.color_matrix.matrix_coeff12.Value()); + const auto r1c3 = s32(slot.color_matrix.matrix_coeff13.Value()); + const auto r2c0 = s32(slot.color_matrix.matrix_coeff20.Value()); + const auto r2c1 = s32(slot.color_matrix.matrix_coeff21.Value()); + const auto r2c2 = s32(slot.color_matrix.matrix_coeff22.Value()); + const auto r2c3 = s32(slot.color_matrix.matrix_coeff23.Value()); + const auto shift = s32(slot.color_matrix.matrix_r_shift.Value()); + const auto clamp_min = s32(slot.config.soft_clamp_low.Value()); + const auto clamp_max = s32(slot.config.soft_clamp_high.Value()); + auto MatMul = [&](const Pixel& in_pixel) -> std::tuple { + auto r = s32(in_pixel.r); + auto g = s32(in_pixel.g); + auto b = s32(in_pixel.b); + r = in_pixel.r * r0c0 + in_pixel.g * r0c1 + in_pixel.b * r0c2; + g = in_pixel.r * r1c0 + in_pixel.g * r1c1 + in_pixel.b * r1c2; + b = in_pixel.r * r2c0 + in_pixel.g * r2c1 + in_pixel.b * r2c2; + r >>= shift; + g >>= shift; + b >>= shift; + r += r0c3; + g += r1c3; + b += r2c3; + r >>= 8; + g >>= 8; + b >>= 8; + return {r, g, b, s32(in_pixel.a)}; + }; + for (u32 y = source_top; y < source_bottom; y++) { + const auto src{y * in_surface_width + source_left}; + const auto dst{y * out_surface_width + rect_left}; + for (u32 x = source_left; x < source_right; x++) { + auto [r, g, b, a] = MatMul(slot_surface[src + x]); + r = std::clamp(r, clamp_min, clamp_max); + g = std::clamp(g, clamp_min, clamp_max); + b = std::clamp(b, clamp_min, clamp_max); + a = std::clamp(a, clamp_min, clamp_max); + output_surface[dst + x] = {u16(r), u16(g), u16(b), u16(a)}; } } } @@ -1011,10 +1018,17 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel for (size_t y = 0; y < surface_height; ++y) { auto const src = y * surface_stride, dst = y * out_luma_stride; for (size_t x = 0; x < surface_width; ++x) { - out[dst + x * 4 + 0] = u8(inp[src + x].r >> 2); - out[dst + x * 4 + 1] = u8(inp[src + x].g >> 2); - out[dst + x * 4 + 2] = u8(inp[src + x].b >> 2); - out[dst + x * 4 + 3] = u8(inp[src + x].a >> 2); + if(format == VideoPixelFormat::A8R8G8B8) { + out[dst + x * 4 + 0] = u8(inp[src + x].b >> 2); + out[dst + x * 4 + 1] = u8(inp[src + x].g >> 2); + out[dst + x * 4 + 2] = u8(inp[src + x].r >> 2); + out[dst + x * 4 + 3] = u8(inp[src + x].a >> 2); + } else { + out[dst + x * 4 + 0] = u8(inp[src + x].r >> 2); + out[dst + x * 4 + 1] = u8(inp[src + x].g >> 2); + out[dst + x * 4 + 2] = u8(inp[src + x].b >> 2); + out[dst + x * 4 + 3] = u8(inp[src + x].a >> 2); + } } } } diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index fef9a5b16e..9ce2ac44bf 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -18,6 +18,7 @@ set(SHADER_FILES blit_color_float.frag block_linear_unswizzle_2d.comp block_linear_unswizzle_3d.comp + block_linear_unswizzle_3d_bcn.comp convert_abgr8_srgb_to_d24s8.frag convert_abgr8_to_d24s8.frag convert_abgr8_to_d32f.frag @@ -87,8 +88,7 @@ set(SHADER_FILES ) if (PLATFORM_HAIKU) - # glslangValidator WILL crash, glslang will not - why? Who the fuck knows - #/boot/home/glslang/build/StandAlone/glslangValidator + # glslangValidator WILL crash, glslang will not set(GLSLANGVALIDATOR "glslang") else() # Normal sane platform who doesn't have a CRASHING glslangValidator diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 6e4535d459..da21b4bde8 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -727,70 +727,35 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui } uint UnquantizeTexelWeight(EncodingData val) { - const uint encoding = Encoding(val); - const uint bitlen = NumBits(val); - const uint bitval = BitValue(val); - const uint A = ReplicateBitTo7((bitval & 1)); - uint B = 0, C = 0, D = 0; - uint result = 0; - const uint bitlen_0_results[5] = {0, 16, 32, 48, 64}; - switch (encoding) { - case JUST_BITS: - return FastReplicateTo6(bitval, bitlen); - case TRIT: { + uint encoding = Encoding(val), bitlen = NumBits(val), bitval = BitValue(val); + if (encoding == JUST_BITS) { + return (bitlen >= 1 && bitlen <= 5) + ? uint(floor(0.5f + float(bitval) * 64.0f / float((1 << bitlen) - 1))) + : FastReplicateTo6(bitval, bitlen); + } else if (encoding == TRIT || encoding == QUINT) { + uint B = 0, C = 0, D = 0; + uint b_mask = (0x3100 >> (bitlen * 4)) & 0xf; + uint b = (bitval >> 1) & b_mask; D = QuintTritValue(val); - switch (bitlen) { - case 0: - return bitlen_0_results[D * 2]; - case 1: { - C = 50; - break; + if (encoding == TRIT) { + switch (bitlen) { + case 0: return D * 32; //0,32,64 + case 1: C = 50; break; + case 2: C = 23; B = (b << 6) | (b << 2) | b; break; + case 3: C = 11; B = (b << 5) | b; break; + } + } else if (encoding == QUINT) { + switch (bitlen) { + case 0: return D * 16; //0, 16, 32, 48, 64 + case 1: C = 28; break; + case 2: C = 13; B = (b << 6) | (b << 1); break; + } } - case 2: { - C = 23; - const uint b = (bitval >> 1) & 1; - B = (b << 6) | (b << 2) | b; - break; - } - case 3: { - C = 11; - const uint cb = (bitval >> 1) & 3; - B = (cb << 5) | cb; - break; - } - default: - break; - } - break; + uint A = ReplicateBitTo7(bitval & 1); + uint res = (A & 0x20) | (((D * C + B) ^ A) >> 2); + return res + (res > 32 ? 1 : 0); } - case QUINT: { - D = QuintTritValue(val); - switch (bitlen) { - case 0: - return bitlen_0_results[D]; - case 1: { - C = 28; - break; - } - case 2: { - C = 13; - const uint b = (bitval >> 1) & 1; - B = (b << 6) | (b << 1); - break; - } - } - break; - } - } - if (encoding != JUST_BITS && bitlen > 0) { - result = D * C + B; - result ^= A; - result = (A & 0x20) | (result >> 2); - } - if (result > 32) { - result += 1; - } - return result; + return 0; } void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) { @@ -1159,10 +1124,11 @@ void DecompressBlock(ivec3 coord) { } uint SwizzleOffset(uvec2 pos) { - const uint x = pos.x; - const uint y = pos.y; - return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + - ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16); + return ((pos.x & 32u) << 3u) | + ((pos.y & 6u) << 5u) | + ((pos.x & 16u) << 1u) | + ((pos.y & 1u) << 4u) | + (pos.x & 15u); } void main() { diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp new file mode 100644 index 0000000000..6e5a300ecd --- /dev/null +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 430 + +#ifdef VULKAN + #extension GL_EXT_shader_16bit_storage : require + #extension GL_EXT_shader_8bit_storage : require + #define HAS_EXTENDED_TYPES 1 + #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { + #define END_PUSH_CONSTANTS }; + #define UNIFORM(n) + #define BINDING_SWIZZLE_BUFFER 0 + #define BINDING_INPUT_BUFFER 1 + #define BINDING_OUTPUT_BUFFER 2 +#else + #extension GL_NV_gpu_shader5 : enable + #ifdef GL_NV_gpu_shader5 + #define HAS_EXTENDED_TYPES 1 + #else + #define HAS_EXTENDED_TYPES 0 + #endif + #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_OUTPUT_BUFFER 0 +#endif + +// --- Push Constants / Uniforms --- +#ifdef VULKAN +layout(push_constant) uniform PushConstants { + uvec3 blocks_dim; // Offset 0 + uint bytes_per_block_log2; // Offset 12 + + uvec3 origin; // Offset 16 + uint slice_size; // Offset 28 + + uint block_size; // Offset 32 + uint x_shift; // Offset 36 + uint block_height; // Offset 40 + uint block_height_mask; // Offset 44 + + uint block_depth; // Offset 48 + uint block_depth_mask; // Offset 52 + int _pad; // Offset 56 + + ivec3 destination; // Offset 60 +} pc; +#else +BEGIN_PUSH_CONSTANTS + UNIFORM(0) uvec3 origin; + UNIFORM(1) ivec3 destination; + UNIFORM(2) uint bytes_per_block_log2; + UNIFORM(3) uint slice_size; + UNIFORM(4) uint block_size; + UNIFORM(5) uint x_shift; + UNIFORM(6) uint block_height; + UNIFORM(7) uint block_height_mask; + UNIFORM(8) uint block_depth; + UNIFORM(9) uint block_depth_mask; + UNIFORM(10) uvec3 blocks_dim; +END_PUSH_CONSTANTS +#define pc // Map pc prefix to nothing for OpenGL compatibility +#endif + +// --- Buffers --- +layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { + uint swizzle_table[]; +}; + +#if HAS_EXTENDED_TYPES + layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; + layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; +#endif +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; }; + +layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer { + uint out_u32[]; +}; + +// --- Constants --- +layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in; + +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 - 1u, GOB_SIZE_Y - 1u); + +// --- Helpers --- +uint SwizzleOffset(uvec2 pos) { + pos &= SWIZZLE_MASK; + return swizzle_table[pos.y * 64u + pos.x]; +} + +uvec4 ReadTexel(uint offset) { + uint bpl2 = pc.bytes_per_block_log2; + switch (bpl2) { +#if HAS_EXTENDED_TYPES + case 0u: return uvec4(u8data[offset], 0u, 0u, 0u); + case 1u: return uvec4(u16data[offset / 2u], 0u, 0u, 0u); +#else + case 0u: return uvec4(bitfieldExtract(u32data[offset / 4u], int((offset * 8u) & 24u), 8), 0u, 0u, 0u); + case 1u: return uvec4(bitfieldExtract(u32data[offset / 4u], int((offset * 8u) & 16u), 16), 0u, 0u, 0u); +#endif + case 2u: return uvec4(u32data[offset / 4u], 0u, 0u, 0u); + case 3u: return uvec4(u64data[offset / 8u], 0u, 0u); + case 4u: return u128data[offset / 16u]; + } + return uvec4(0u); +} + +void main() { + uvec3 block_coord = gl_GlobalInvocationID; + if (any(greaterThanEqual(block_coord, pc.blocks_dim))) { + return; + } + + uint bytes_per_block = 1u << pc.bytes_per_block_log2; + // Origin is in pixels, divide by 4 for block-space (e.g. BCn formats) + uvec3 pos; + pos.x = (block_coord.x + (pc.origin.x >> 2u)) * bytes_per_block; + pos.y = block_coord.y + (pc.origin.y >> 2u); + pos.z = block_coord.z + pc.origin.z; + + uint swizzle = SwizzleOffset(pos.xy); + uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; + uint offset = 0u; + // Apply block-linear offsets + offset += (pos.z >> pc.block_depth) * pc.slice_size; + offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height); + offset += (block_y >> pc.block_height) * pc.block_size; + offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT; + offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift; + offset += swizzle; + + uvec4 texel = ReadTexel(offset); + + // Calculate linear output index + uint block_index = block_coord.x + + (block_coord.y * pc.blocks_dim.x) + + (block_coord.z * pc.blocks_dim.x * pc.blocks_dim.y); + uint out_idx = block_index * (bytes_per_block >> 2u); + + out_u32[out_idx] = texel.x; + out_u32[out_idx + 1u] = texel.y; + if (pc.bytes_per_block_log2 == 4u) { + out_u32[out_idx + 2u] = texel.z; + out_u32[out_idx + 3u] = texel.w; + } +} \ No newline at end of file diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp new file mode 100644 index 0000000000..3fe69be4dd --- /dev/null +++ b/src/video_core/macro.cpp @@ -0,0 +1,1667 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include +#ifdef ARCHITECTURE_x86_64 +// xbyak hates human beings +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#endif +#include +#endif + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "common/container_hash.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/engines/draw_manager.h" +#include "video_core/dirty_flags.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/macro.h" + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/logging/log.h" +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/xbyak_abi.h" +#include "common/x64/xbyak_util.h" +#endif +#include "video_core/engines/maxwell_3d.h" + +namespace Tegra { + +using Maxwell3D = Engines::Maxwell3D; + +namespace { + +bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { + switch (topology) { + case Maxwell3D::Regs::PrimitiveTopology::Points: + case Maxwell3D::Regs::PrimitiveTopology::Lines: + case Maxwell3D::Regs::PrimitiveTopology::LineLoop: + case Maxwell3D::Regs::PrimitiveTopology::LineStrip: + case Maxwell3D::Regs::PrimitiveTopology::Triangles: + case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip: + case Maxwell3D::Regs::PrimitiveTopology::TriangleFan: + case Maxwell3D::Regs::PrimitiveTopology::LinesAdjacency: + case Maxwell3D::Regs::PrimitiveTopology::LineStripAdjacency: + case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency: + case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency: + case Maxwell3D::Regs::PrimitiveTopology::Patches: + return true; + case Maxwell3D::Regs::PrimitiveTopology::Quads: + case Maxwell3D::Regs::PrimitiveTopology::QuadStrip: + case Maxwell3D::Regs::PrimitiveTopology::Polygon: + default: + return false; + } +} + +class HLEMacroImpl : public CachedMacro { +public: + explicit HLEMacroImpl(Maxwell3D& maxwell3d_) + : CachedMacro(maxwell3d_) + {} +}; + +/// @note: these macros have two versions, a normal and extended version, with the extended version +/// also assigning the base vertex/instance. +template +class HLE_DrawArraysIndirect final : public HLEMacroImpl { +public: + explicit HLE_DrawArraysIndirect(Maxwell3D& maxwell3d_) + : HLEMacroImpl(maxwell3d_) + {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(parameters); + return; + } + + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 4 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; + + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawArrayIndirect(topology); + + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + } + +private: + void Fallback(const std::vector& parameters) { + SCOPE_EXIT { + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + }; + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + + auto topology = static_cast(parameters[0]); + const u32 vertex_first = parameters[3]; + const u32 vertex_count = parameters[1]; + + if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { + ASSERT(false && "Faulty draw!"); + return; + } + + const u32 base_instance = parameters[4]; + if (extended) { + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, + instance_count); + + if (extended) { + maxwell3d.regs.global_base_instance_index = 0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + } +}; + +/* + * @note: these macros have two versions, a normal and extended version, with the extended version + * also assigning the base vertex/instance. + */ +template +class HLE_DrawIndexedIndirect final : public HLEMacroImpl { +public: + explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(parameters); + return; + } + + const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 5 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + } + +private: + void Fallback(const std::vector& parameters) { + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); + + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + } +}; + +class HLE_MultiLayerClear final : public HLEMacroImpl { +public: + explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + ASSERT(parameters.size() == 1); + + const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; + const u32 rt_index = clear_params.RT; + const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; + ASSERT(clear_params.layer == 0); + + maxwell3d.regs.clear_surface.raw = clear_params.raw; + maxwell3d.draw_manager->Clear(num_layers); + } +}; + +class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl { +public: + explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); + if (!IsTopologySafe(topology)) { + Fallback(parameters); + return; + } + + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + + const u32 padding = parameters[3]; // padding is in words + + // size of each indirect segment + const u32 indirect_words = 5 + padding; + const u32 stride = indirect_words * sizeof(u32); + const std::size_t draw_count = end_indirect - start_indirect; + const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = true; + params.count_start_address = maxwell3d.GetMacroAddress(4); + params.indirect_start_address = maxwell3d.GetMacroAddress(5); + params.buffer_size = stride * draw_count; + params.max_draw_counts = draw_count; + params.stride = stride; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.SetHLEReplacementAttributeType(0, 0x648, + Maxwell3D::HLEReplacementAttributeType::DrawID); + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + +private: + void Fallback(const std::vector& parameters) { + SCOPE_EXIT { + // Clean everything. + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + }; + maxwell3d.RefreshParameters(); + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + const auto topology = static_cast(parameters[2]); + const u32 padding = parameters[3]; + const std::size_t max_draws = parameters[4]; + + const u32 indirect_words = 5 + padding; + const std::size_t first_draw = start_indirect; + const std::size_t effective_draws = end_indirect - start_indirect; + const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); + + for (std::size_t index = first_draw; index < last_draw; index++) { + const std::size_t base = index * indirect_words + 5; + const u32 base_vertex = parameters[base + 3]; + const u32 base_instance = parameters[base + 4]; + maxwell3d.regs.vertex_id_base = base_vertex; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.CallMethod(0x8e3, 0x648, true); + maxwell3d.CallMethod(0x8e4, static_cast(index), true); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], + base_vertex, base_instance, parameters[base + 1]); + } + } +}; + +class HLE_DrawIndirectByteCount final : public HLEMacroImpl { +public: + explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); + + auto topology = static_cast(parameters[0] & 0xFFFFU); + if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { + Fallback(parameters); + return; + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = true; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(2); + params.buffer_size = 4; + params.max_draw_counts = 1; + params.stride = parameters[1]; + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + + maxwell3d.draw_manager->DrawArrayIndirect(topology); + } + +private: + void Fallback(const std::vector& parameters) { + maxwell3d.RefreshParameters(); + + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + + maxwell3d.draw_manager->DrawArray( + maxwell3d.regs.draw.topology, 0, + maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); + } +}; + +class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { +public: + explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; + const u32 address = maxwell3d.regs.shadow_scratch[24]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = 0x7000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + const_buffer.offset = offset; + } +}; + +class HLE_D7333D26E0A93EDE final : public HLEMacroImpl { +public: + explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const size_t index = parameters[0]; + const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; + const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = size; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + } +}; + +class HLE_BindShader final : public HLEMacroImpl { +public: + explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + auto& regs = maxwell3d.regs; + const u32 index = parameters[0]; + if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { + return; + } + + regs.pipelines[index & 0xF].offset = parameters[2]; + maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; + regs.shadow_scratch[28 + index] = parameters[1]; + regs.shadow_scratch[34 + index] = parameters[2]; + + const u32 address = parameters[4]; + auto& const_buffer = regs.const_buffer; + const_buffer.size = 0x10000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + + const size_t bind_group_id = parameters[3] & 0x7F; + auto& bind_group = regs.bind_groups[bind_group_id]; + bind_group.raw_config = 0x11; + maxwell3d.ProcessCBBind(bind_group_id); + } +}; + +class HLE_SetRasterBoundingBox final : public HLEMacroImpl { +public: + explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const u32 raster_mode = parameters[0]; + auto& regs = maxwell3d.regs; + const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; + const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; + regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; + regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); + } +}; + +template +class HLE_ClearConstBuffer final : public HLEMacroImpl { +public: + explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + static constexpr std::array zeroes{}; + auto& regs = maxwell3d.regs; + regs.const_buffer.size = u32(base_size); + regs.const_buffer.address_high = parameters[0]; + regs.const_buffer.address_low = parameters[1]; + regs.const_buffer.offset = 0; + maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); + } +}; + +class HLE_ClearMemory final : public HLEMacroImpl { +public: + explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + + const u32 needed_memory = parameters[2] / sizeof(u32); + if (needed_memory > zero_memory.size()) { + zero_memory.resize(needed_memory, 0); + } + auto& regs = maxwell3d.regs; + regs.upload.line_length_in = parameters[2]; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); + } + +private: + std::vector zero_memory; +}; + +class HLE_TransformFeedbackSetup final : public HLEMacroImpl { +public: + explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + + auto& regs = maxwell3d.regs; + regs.transform_feedback_enabled = 1; + regs.transform_feedback.buffers[0].start_offset = 0; + regs.transform_feedback.buffers[1].start_offset = 0; + regs.transform_feedback.buffers[2].start_offset = 0; + regs.transform_feedback.buffers[3].start_offset = 0; + + regs.upload.line_length_in = 4; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); + + maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); + } +}; + +} // Anonymous namespace + +HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} + +HLEMacro::~HLEMacro() = default; + +std::unique_ptr HLEMacro::GetHLEProgram(u64 hash) const { + // Compiler will make you a GREAT job at making an ad-hoc hash table :) + switch (hash) { + case 0x0D61FC9FAAC9FCADULL: return std::make_unique>(maxwell3d); + case 0x8A4D173EB99A8603ULL: return std::make_unique>(maxwell3d); + case 0x771BB18C62444DA0ULL: return std::make_unique>(maxwell3d); + case 0x0217920100488FF7ULL: return std::make_unique>(maxwell3d); + case 0x3F5E74B9C9A50164ULL: return std::make_unique(maxwell3d); + case 0xEAD26C3E2109B06BULL: return std::make_unique(maxwell3d); + case 0xC713C83D8F63CCF3ULL: return std::make_unique(maxwell3d); + case 0xD7333D26E0A93EDEULL: return std::make_unique(maxwell3d); + case 0xEB29B2A09AA06D38ULL: return std::make_unique(maxwell3d); + case 0xDB1341DBEB4C8AF7ULL: return std::make_unique(maxwell3d); + case 0x6C97861D891EDf7EULL: return std::make_unique>(maxwell3d); + case 0xD246FDDF3A6173D7ULL: return std::make_unique>(maxwell3d); + case 0xEE4D0004BEC8ECF4ULL: return std::make_unique(maxwell3d); + case 0xFC0CF27F5FFAA661ULL: return std::make_unique(maxwell3d); + case 0xB5F74EDB717278ECULL: return std::make_unique(maxwell3d); + default: + return nullptr; + } +} + +namespace { +class MacroInterpreterImpl final : public CachedMacro { +public: + explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) + : CachedMacro(maxwell3d_) + , code{code_} + {} + + void Execute(const std::vector& params, u32 method) override; + +private: + /// Resets the execution engine state, zeroing registers, etc. + void Reset(); + + /** + * Executes a single macro instruction located at the current program counter. Returns whether + * the interpreter should keep running. + * + * @param is_delay_slot Whether the current step is being executed due to a delay slot in a + * previous instruction. + */ + bool Step(bool is_delay_slot); + + /// Calculates the result of an ALU operation. src_a OP src_b; + u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); + + /// Performs the result operation on the input result and stores it in the specified register + /// (if necessary). + void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result); + + /// Evaluates the branch condition and returns whether the branch should be taken or not. + bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; + + /// Reads an opcode at the current program counter location. + Macro::Opcode GetOpcode() const; + + /// Returns the specified register's value. Register 0 is hardcoded to always return 0. + u32 GetRegister(u32 register_id) const; + + /// Sets the register to the input value. + void SetRegister(u32 register_id, u32 value); + + /// Sets the method address to use for the next Send instruction. + void SetMethodAddress(u32 address); + + /// Calls a GPU Engine method with the input parameter. + void Send(u32 value); + + /// Reads a GPU register located at the method address. + u32 Read(u32 method) const; + + /// Returns the next parameter in the parameter queue. + u32 FetchParameter(); + + /// Current program counter + u32 pc{}; + /// Program counter to execute at after the delay slot is executed. + std::optional delayed_pc; + + /// General purpose macro registers. + std::array registers = {}; + + /// Method address to use for the next Send instruction. + Macro::MethodAddress method_address = {}; + + /// Input parameters of the current macro. + std::unique_ptr parameters; + std::size_t num_parameters = 0; + std::size_t parameters_capacity = 0; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; + + bool carry_flag = false; + const std::vector& code; +}; + +void MacroInterpreterImpl::Execute(const std::vector& params, u32 method) { + Reset(); + + registers[1] = params[0]; + num_parameters = params.size(); + + if (num_parameters > parameters_capacity) { + parameters_capacity = num_parameters; + parameters = std::make_unique(num_parameters); + } + std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32)); + + // Execute the code until we hit an exit condition. + bool keep_executing = true; + while (keep_executing) { + keep_executing = Step(false); + } + + // Assert the the macro used all the input parameters + ASSERT(next_parameter_index == num_parameters); +} + +void MacroInterpreterImpl::Reset() { + registers = {}; + pc = 0; + delayed_pc = {}; + method_address.raw = 0; + num_parameters = 0; + // The next parameter index starts at 1, because $r1 already has the value of the first + // parameter. + next_parameter_index = 1; + carry_flag = false; +} + +bool MacroInterpreterImpl::Step(bool is_delay_slot) { + u32 base_address = pc; + + Macro::Opcode opcode = GetOpcode(); + pc += 4; + + // Update the program counter if we were delayed + if (delayed_pc) { + ASSERT(is_delay_slot); + pc = *delayed_pc; + delayed_pc = {}; + } + + switch (opcode.operation) { + case Macro::Operation::ALU: { + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), + GetRegister(opcode.src_b)); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Macro::Operation::AddImmediate: { + ProcessResult(opcode.result_operation, opcode.dst, + GetRegister(opcode.src_a) + opcode.immediate); + break; + } + case Macro::Operation::ExtractInsert: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); + dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); + dst |= src << opcode.bf_dst_bit; + ProcessResult(opcode.result_operation, opcode.dst, dst); + break; + } + case Macro::Operation::ExtractShiftLeftImmediate: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Macro::Operation::ExtractShiftLeftRegister: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Macro::Operation::Read: { + u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Macro::Operation::Branch: { + ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); + u32 value = GetRegister(opcode.src_a); + bool taken = EvaluateBranchCondition(opcode.branch_condition, value); + if (taken) { + // Ignore the delay slot if the branch has the annul bit. + if (opcode.branch_annul) { + pc = base_address + opcode.GetBranchTarget(); + return true; + } + + delayed_pc = base_address + opcode.GetBranchTarget(); + // Execute one more instruction due to the delay slot. + return Step(true); + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value()); + break; + } + + // An instruction with the Exit flag will not actually + // cause an exit if it's executed inside a delay slot. + if (opcode.is_exit && !is_delay_slot) { + // Exit has a delay slot, execute the next instruction + Step(true); + return false; + } + + return true; +} + +u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) { + switch (operation) { + case Macro::ALUOperation::Add: { + const u64 result{static_cast(src_a) + src_b}; + carry_flag = result > 0xffffffff; + return static_cast(result); + } + case Macro::ALUOperation::AddWithCarry: { + const u64 result{static_cast(src_a) + src_b + (carry_flag ? 1ULL : 0ULL)}; + carry_flag = result > 0xffffffff; + return static_cast(result); + } + case Macro::ALUOperation::Subtract: { + const u64 result{static_cast(src_a) - src_b}; + carry_flag = result < 0x100000000; + return static_cast(result); + } + case Macro::ALUOperation::SubtractWithBorrow: { + const u64 result{static_cast(src_a) - src_b - (carry_flag ? 0ULL : 1ULL)}; + carry_flag = result < 0x100000000; + return static_cast(result); + } + case Macro::ALUOperation::Xor: + return src_a ^ src_b; + case Macro::ALUOperation::Or: + return src_a | src_b; + case Macro::ALUOperation::And: + return src_a & src_b; + case Macro::ALUOperation::AndNot: + return src_a & ~src_b; + case Macro::ALUOperation::Nand: + return ~(src_a & src_b); + + default: + UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", operation); + return 0; + } +} + +void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) { + switch (operation) { + case Macro::ResultOperation::IgnoreAndFetch: + // Fetch parameter and ignore result. + SetRegister(reg, FetchParameter()); + break; + case Macro::ResultOperation::Move: + // Move result. + SetRegister(reg, result); + break; + case Macro::ResultOperation::MoveAndSetMethod: + // Move result and use as Method Address. + SetRegister(reg, result); + SetMethodAddress(result); + break; + case Macro::ResultOperation::FetchAndSend: + // Fetch parameter and send result. + SetRegister(reg, FetchParameter()); + Send(result); + break; + case Macro::ResultOperation::MoveAndSend: + // Move and send result. + SetRegister(reg, result); + Send(result); + break; + case Macro::ResultOperation::FetchAndSetMethod: + // Fetch parameter and use result as Method Address. + SetRegister(reg, FetchParameter()); + SetMethodAddress(result); + break; + case Macro::ResultOperation::MoveAndSetMethodFetchAndSend: + // Move result and use as Method Address, then fetch and send parameter. + SetRegister(reg, result); + SetMethodAddress(result); + Send(FetchParameter()); + break; + case Macro::ResultOperation::MoveAndSetMethodSend: + // Move result and use as Method Address, then send bits 12:17 of result. + SetRegister(reg, result); + SetMethodAddress(result); + Send((result >> 12) & 0b111111); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); + break; + } +} + +bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const { + switch (cond) { + case Macro::BranchCondition::Zero: + return value == 0; + case Macro::BranchCondition::NotZero: + return value != 0; + } + UNREACHABLE(); +} + +Macro::Opcode MacroInterpreterImpl::GetOpcode() const { + ASSERT((pc % sizeof(u32)) == 0); + ASSERT(pc < code.size() * sizeof(u32)); + return {code[pc / sizeof(u32)]}; +} + +u32 MacroInterpreterImpl::GetRegister(u32 register_id) const { + return registers.at(register_id); +} + +void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { + // Register 0 is hardwired as the zero register. + // Ensure no writes to it actually occur. + if (register_id == 0) { + return; + } + + registers.at(register_id) = value; +} + +void MacroInterpreterImpl::SetMethodAddress(u32 address) { + method_address.raw = address; +} + +void MacroInterpreterImpl::Send(u32 value) { + maxwell3d.CallMethod(method_address.address, value, true); + // Increment the method address by the method increment. + method_address.address.Assign(method_address.address.Value() + + method_address.increment.Value()); +} + +u32 MacroInterpreterImpl::Read(u32 method) const { + return maxwell3d.GetRegisterValue(method); +} + +u32 MacroInterpreterImpl::FetchParameter() { + ASSERT(next_parameter_index < num_parameters); + return parameters[next_parameter_index++]; +} +} // Anonymous namespace + +#ifdef ARCHITECTURE_x86_64 +namespace { +constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx; +constexpr Xbyak::Reg32 RESULT = Xbyak::util::r10d; +constexpr Xbyak::Reg64 MAX_PARAMETER = Xbyak::util::r11; +constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12; +constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d; +constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; + +constexpr std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({ + STATE, + RESULT, + MAX_PARAMETER, + PARAMETERS, + METHOD_ADDRESS, + BRANCH_HOLDER, +}); + +// Arbitrarily chosen based on current booting games. +constexpr size_t MAX_CODE_SIZE = 0x10000; + +std::bitset<32> PersistentCallerSavedRegs() { + return PERSISTENT_REGISTERS & Common::X64::ABI_ALL_CALLER_SAVED; +} + +/// @brief Must enforce W^X constraints, as we yet don't havea global "NO_EXECUTE" support flag +/// the speed loss is minimal, and in fact may be negligible, however for your peace of mind +/// I simply included known OSes whom had W^X issues +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +static const auto default_cg_mode = Xbyak::DontSetProtectRWE; +#else +static const auto default_cg_mode = nullptr; //Allow RWE +#endif + +class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { +public: + explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) + : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) + , CachedMacro(maxwell3d_) + , code{code_} + { + Compile(); + } + + void Execute(const std::vector& parameters, u32 method) override; + + void Compile_ALU(Macro::Opcode opcode); + void Compile_AddImmediate(Macro::Opcode opcode); + void Compile_ExtractInsert(Macro::Opcode opcode); + void Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode); + void Compile_ExtractShiftLeftRegister(Macro::Opcode opcode); + void Compile_Read(Macro::Opcode opcode); + void Compile_Branch(Macro::Opcode opcode); + +private: + void Optimizer_ScanFlags(); + + void Compile(); + bool Compile_NextInstruction(); + + Xbyak::Reg32 Compile_FetchParameter(); + Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); + + void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); + void Compile_Send(Xbyak::Reg32 value); + + Macro::Opcode GetOpCode() const; + + struct JITState { + Engines::Maxwell3D* maxwell3d{}; + std::array registers{}; + u32 carry_flag{}; + }; + static_assert(offsetof(JITState, maxwell3d) == 0, "Maxwell3D is not at 0x0"); + using ProgramType = void (*)(JITState*, const u32*, const u32*); + + struct OptimizerState { + bool can_skip_carry{}; + bool has_delayed_pc{}; + bool zero_reg_skip{}; + bool skip_dummy_addimmediate{}; + bool optimize_for_method_move{}; + bool enable_asserts{}; + }; + OptimizerState optimizer{}; + + std::optional next_opcode{}; + ProgramType program{nullptr}; + + std::array labels; + std::array delay_skip; + Xbyak::Label end_of_code{}; + + bool is_delay_slot{}; + u32 pc{}; + + const std::vector& code; +}; + +void MacroJITx64Impl::Execute(const std::vector& parameters, u32 method) { + ASSERT_OR_EXECUTE(program != nullptr, { return; }); + JITState state{}; + state.maxwell3d = &maxwell3d; + state.registers = {}; + program(&state, parameters.data(), parameters.data() + parameters.size()); +} + +void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { + const bool is_a_zero = opcode.src_a == 0; + const bool is_b_zero = opcode.src_b == 0; + const bool valid_operation = !is_a_zero && !is_b_zero; + [[maybe_unused]] const bool is_move_operation = !is_a_zero && is_b_zero; + const bool has_zero_register = is_a_zero || is_b_zero; + const bool no_zero_reg_skip = opcode.alu_operation == Macro::ALUOperation::AddWithCarry || + opcode.alu_operation == Macro::ALUOperation::SubtractWithBorrow; + + Xbyak::Reg32 src_a; + Xbyak::Reg32 src_b; + + if (!optimizer.zero_reg_skip || no_zero_reg_skip) { + src_a = Compile_GetRegister(opcode.src_a, RESULT); + src_b = Compile_GetRegister(opcode.src_b, eax); + } else { + if (!is_a_zero) { + src_a = Compile_GetRegister(opcode.src_a, RESULT); + } + if (!is_b_zero) { + src_b = Compile_GetRegister(opcode.src_b, eax); + } + } + + bool has_emitted = false; + + switch (opcode.alu_operation) { + case Macro::ALUOperation::Add: + if (optimizer.zero_reg_skip) { + if (valid_operation) { + add(src_a, src_b); + } + } else { + add(src_a, src_b); + } + + if (!optimizer.can_skip_carry) { + setc(byte[STATE + offsetof(JITState, carry_flag)]); + } + break; + case Macro::ALUOperation::AddWithCarry: + bt(dword[STATE + offsetof(JITState, carry_flag)], 0); + adc(src_a, src_b); + setc(byte[STATE + offsetof(JITState, carry_flag)]); + break; + case Macro::ALUOperation::Subtract: + if (optimizer.zero_reg_skip) { + if (valid_operation) { + sub(src_a, src_b); + has_emitted = true; + } + } else { + sub(src_a, src_b); + has_emitted = true; + } + if (!optimizer.can_skip_carry && has_emitted) { + setc(byte[STATE + offsetof(JITState, carry_flag)]); + } + break; + case Macro::ALUOperation::SubtractWithBorrow: + bt(dword[STATE + offsetof(JITState, carry_flag)], 0); + sbb(src_a, src_b); + setc(byte[STATE + offsetof(JITState, carry_flag)]); + break; + case Macro::ALUOperation::Xor: + if (optimizer.zero_reg_skip) { + if (valid_operation) { + xor_(src_a, src_b); + } + } else { + xor_(src_a, src_b); + } + break; + case Macro::ALUOperation::Or: + if (optimizer.zero_reg_skip) { + if (valid_operation) { + or_(src_a, src_b); + } + } else { + or_(src_a, src_b); + } + break; + case Macro::ALUOperation::And: + if (optimizer.zero_reg_skip) { + if (!has_zero_register) { + and_(src_a, src_b); + } + } else { + and_(src_a, src_b); + } + break; + case Macro::ALUOperation::AndNot: + if (optimizer.zero_reg_skip) { + if (!is_a_zero) { + not_(src_b); + and_(src_a, src_b); + } + } else { + not_(src_b); + and_(src_a, src_b); + } + break; + case Macro::ALUOperation::Nand: + if (optimizer.zero_reg_skip) { + if (!is_a_zero) { + and_(src_a, src_b); + not_(src_a); + } + } else { + and_(src_a, src_b); + not_(src_a); + } + break; + default: + UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value()); + break; + } + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) { + if (optimizer.skip_dummy_addimmediate) { + // Games tend to use this as an exit instruction placeholder. It's to encode an instruction + // without doing anything. In our case we can just not emit anything. + if (opcode.result_operation == Macro::ResultOperation::Move && opcode.dst == 0) { + return; + } + } + // Check for redundant moves + if (optimizer.optimize_for_method_move && + opcode.result_operation == Macro::ResultOperation::MoveAndSetMethod) { + if (next_opcode.has_value()) { + const auto next = *next_opcode; + if (next.result_operation == Macro::ResultOperation::MoveAndSetMethod && + opcode.dst == next.dst) { + return; + } + } + } + if (optimizer.zero_reg_skip && opcode.src_a == 0) { + if (opcode.immediate == 0) { + xor_(RESULT, RESULT); + } else { + mov(RESULT, opcode.immediate); + } + } else { + auto result = Compile_GetRegister(opcode.src_a, RESULT); + if (opcode.immediate > 2) { + add(result, opcode.immediate); + } else if (opcode.immediate == 1) { + inc(result); + } else if (opcode.immediate < 0) { + sub(result, opcode.immediate * -1); + } + } + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) { + auto dst = Compile_GetRegister(opcode.src_a, RESULT); + auto src = Compile_GetRegister(opcode.src_b, eax); + + const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); + and_(dst, mask); + shr(src, opcode.bf_src_bit); + and_(src, opcode.GetBitfieldMask()); + shl(src, opcode.bf_dst_bit); + or_(dst, src); + + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) { + const auto dst = Compile_GetRegister(opcode.src_a, ecx); + const auto src = Compile_GetRegister(opcode.src_b, RESULT); + + shr(src, dst.cvt8()); + and_(src, opcode.GetBitfieldMask()); + shl(src, opcode.bf_dst_bit); + + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) { + const auto dst = Compile_GetRegister(opcode.src_a, ecx); + const auto src = Compile_GetRegister(opcode.src_b, RESULT); + + shr(src, opcode.bf_src_bit); + and_(src, opcode.GetBitfieldMask()); + shl(src, dst.cvt8()); + + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { + if (optimizer.zero_reg_skip && opcode.src_a == 0) { + if (opcode.immediate == 0) { + xor_(RESULT, RESULT); + } else { + mov(RESULT, opcode.immediate); + } + } else { + auto result = Compile_GetRegister(opcode.src_a, RESULT); + if (opcode.immediate > 2) { + add(result, opcode.immediate); + } else if (opcode.immediate == 1) { + inc(result); + } else if (opcode.immediate < 0) { + sub(result, opcode.immediate * -1); + } + } + + // Equivalent to Engines::Maxwell3D::GetRegisterValue: + if (optimizer.enable_asserts) { + Xbyak::Label pass_range_check; + cmp(RESULT, static_cast(Engines::Maxwell3D::Regs::NUM_REGS)); + jb(pass_range_check); + int3(); + L(pass_range_check); + } + mov(rax, qword[STATE]); + mov(RESULT, + dword[rax + offsetof(Engines::Maxwell3D, regs) + + offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]); + + Compile_ProcessResult(opcode.result_operation, opcode.dst); +} + +void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { + maxwell3d->CallMethod(method_address.address, value, true); +} + +void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { + Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + mov(Common::X64::ABI_PARAM1, qword[STATE]); + mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); + mov(Common::X64::ABI_PARAM3.cvt32(), value); + Common::X64::CallFarFunction(*this, &Send); + Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + + Xbyak::Label dont_process{}; + // Get increment + test(METHOD_ADDRESS, 0x3f000); + // If zero, method address doesn't update + je(dont_process); + + mov(ecx, METHOD_ADDRESS); + and_(METHOD_ADDRESS, 0xfff); + shr(ecx, 12); + and_(ecx, 0x3f); + lea(eax, ptr[rcx + METHOD_ADDRESS.cvt64()]); + sal(ecx, 12); + or_(eax, ecx); + + mov(METHOD_ADDRESS, eax); + + L(dont_process); +} + +void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) { + ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); + const s32 jump_address = + static_cast(pc) + static_cast(opcode.GetBranchTarget() / sizeof(s32)); + + Xbyak::Label end; + auto value = Compile_GetRegister(opcode.src_a, eax); + cmp(value, 0); // test(value, value); + if (optimizer.has_delayed_pc) { + switch (opcode.branch_condition) { + case Macro::BranchCondition::Zero: + jne(end, T_NEAR); + break; + case Macro::BranchCondition::NotZero: + je(end, T_NEAR); + break; + } + + if (opcode.branch_annul) { + xor_(BRANCH_HOLDER, BRANCH_HOLDER); + jmp(labels[jump_address], T_NEAR); + } else { + Xbyak::Label handle_post_exit{}; + Xbyak::Label skip{}; + jmp(skip, T_NEAR); + + L(handle_post_exit); + xor_(BRANCH_HOLDER, BRANCH_HOLDER); + jmp(labels[jump_address], T_NEAR); + + L(skip); + mov(BRANCH_HOLDER, handle_post_exit); + jmp(delay_skip[pc], T_NEAR); + } + } else { + switch (opcode.branch_condition) { + case Macro::BranchCondition::Zero: + je(labels[jump_address], T_NEAR); + break; + case Macro::BranchCondition::NotZero: + jne(labels[jump_address], T_NEAR); + break; + } + } + + L(end); +} + +void MacroJITx64Impl::Optimizer_ScanFlags() { + optimizer.can_skip_carry = true; + optimizer.has_delayed_pc = false; + for (auto raw_op : code) { + Macro::Opcode op{}; + op.raw = raw_op; + + if (op.operation == Macro::Operation::ALU) { + // Scan for any ALU operations which actually use the carry flag, if they don't exist in + // our current code we can skip emitting the carry flag handling operations + if (op.alu_operation == Macro::ALUOperation::AddWithCarry || + op.alu_operation == Macro::ALUOperation::SubtractWithBorrow) { + optimizer.can_skip_carry = false; + } + } + + if (op.operation == Macro::Operation::Branch) { + if (!op.branch_annul) { + optimizer.has_delayed_pc = true; + } + } + } +} + +void MacroJITx64Impl::Compile() { + labels.fill(Xbyak::Label()); + + Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); + // JIT state + mov(STATE, Common::X64::ABI_PARAM1); + mov(PARAMETERS, Common::X64::ABI_PARAM2); + mov(MAX_PARAMETER, Common::X64::ABI_PARAM3); + xor_(RESULT, RESULT); + xor_(METHOD_ADDRESS, METHOD_ADDRESS); + xor_(BRANCH_HOLDER, BRANCH_HOLDER); + + mov(dword[STATE + offsetof(JITState, registers) + 4], Compile_FetchParameter()); + + // Track get register for zero registers and mark it as no-op + optimizer.zero_reg_skip = true; + + // AddImmediate tends to be used as a NOP instruction, if we detect this we can + // completely skip the entire code path and no emit anything + optimizer.skip_dummy_addimmediate = true; + + // SMO tends to emit a lot of unnecessary method moves, we can mitigate this by only emitting + // one if our register isn't "dirty" + optimizer.optimize_for_method_move = true; + + // Enable run-time assertions in JITted code + optimizer.enable_asserts = false; + + // Check to see if we can skip emitting certain instructions + Optimizer_ScanFlags(); + + const u32 op_count = static_cast(code.size()); + for (u32 i = 0; i < op_count; i++) { + if (i < op_count - 1) { + pc = i + 1; + next_opcode = GetOpCode(); + } else { + next_opcode = {}; + } + pc = i; + Compile_NextInstruction(); + } + + L(end_of_code); + + Common::X64::ABI_PopRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); + ret(); + ready(); + program = getCode(); +} + +bool MacroJITx64Impl::Compile_NextInstruction() { + const auto opcode = GetOpCode(); + if (labels[pc].getAddress()) { + return false; + } + + L(labels[pc]); + + switch (opcode.operation) { + case Macro::Operation::ALU: + Compile_ALU(opcode); + break; + case Macro::Operation::AddImmediate: + Compile_AddImmediate(opcode); + break; + case Macro::Operation::ExtractInsert: + Compile_ExtractInsert(opcode); + break; + case Macro::Operation::ExtractShiftLeftImmediate: + Compile_ExtractShiftLeftImmediate(opcode); + break; + case Macro::Operation::ExtractShiftLeftRegister: + Compile_ExtractShiftLeftRegister(opcode); + break; + case Macro::Operation::Read: + Compile_Read(opcode); + break; + case Macro::Operation::Branch: + Compile_Branch(opcode); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented opcode {}", opcode.operation.Value()); + break; + } + + if (optimizer.has_delayed_pc) { + if (opcode.is_exit) { + mov(rax, end_of_code); + test(BRANCH_HOLDER, BRANCH_HOLDER); + cmove(BRANCH_HOLDER, rax); + // Jump to next instruction to skip delay slot check + je(labels[pc + 1], T_NEAR); + } else { + // TODO(ogniK): Optimize delay slot branching + Xbyak::Label no_delay_slot{}; + test(BRANCH_HOLDER, BRANCH_HOLDER); + je(no_delay_slot, T_NEAR); + mov(rax, BRANCH_HOLDER); + xor_(BRANCH_HOLDER, BRANCH_HOLDER); + jmp(rax); + L(no_delay_slot); + } + L(delay_skip[pc]); + if (opcode.is_exit) { + return false; + } + } else { + test(BRANCH_HOLDER, BRANCH_HOLDER); + jne(end_of_code, T_NEAR); + if (opcode.is_exit) { + inc(BRANCH_HOLDER); + return false; + } + } + return true; +} + +static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { + LOG_CRITICAL(HW_GPU, + "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", + parameter, max_parameter - sizeof(u32)); +} + +Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { + Xbyak::Label parameter_ok{}; + cmp(PARAMETERS, MAX_PARAMETER); + jb(parameter_ok, T_NEAR); + Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + mov(Common::X64::ABI_PARAM1, PARAMETERS); + mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); + Common::X64::CallFarFunction(*this, &WarnInvalidParameter); + Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + L(parameter_ok); + mov(eax, dword[PARAMETERS]); + add(PARAMETERS, sizeof(u32)); + return eax; +} + +Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) { + if (index == 0) { + // Register 0 is always zero + xor_(dst, dst); + } else { + mov(dst, dword[STATE + offsetof(JITState, registers) + index * sizeof(u32)]); + } + + return dst; +} + +void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) { + const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) { + // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero + // register. + if (reg_index == 0) { + return; + } + mov(dword[STATE + offsetof(JITState, registers) + reg_index * sizeof(u32)], result); + }; + const auto SetMethodAddress = [this](const Xbyak::Reg32& reg32) { mov(METHOD_ADDRESS, reg32); }; + + switch (operation) { + case Macro::ResultOperation::IgnoreAndFetch: + SetRegister(reg, Compile_FetchParameter()); + break; + case Macro::ResultOperation::Move: + SetRegister(reg, RESULT); + break; + case Macro::ResultOperation::MoveAndSetMethod: + SetRegister(reg, RESULT); + SetMethodAddress(RESULT); + break; + case Macro::ResultOperation::FetchAndSend: + // Fetch parameter and send result. + SetRegister(reg, Compile_FetchParameter()); + Compile_Send(RESULT); + break; + case Macro::ResultOperation::MoveAndSend: + // Move and send result. + SetRegister(reg, RESULT); + Compile_Send(RESULT); + break; + case Macro::ResultOperation::FetchAndSetMethod: + // Fetch parameter and use result as Method Address. + SetRegister(reg, Compile_FetchParameter()); + SetMethodAddress(RESULT); + break; + case Macro::ResultOperation::MoveAndSetMethodFetchAndSend: + // Move result and use as Method Address, then fetch and send parameter. + SetRegister(reg, RESULT); + SetMethodAddress(RESULT); + Compile_Send(Compile_FetchParameter()); + break; + case Macro::ResultOperation::MoveAndSetMethodSend: + // Move result and use as Method Address, then send bits 12:17 of result. + SetRegister(reg, RESULT); + SetMethodAddress(RESULT); + shr(RESULT, 12); + and_(RESULT, 0b111111); + Compile_Send(RESULT); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation); + break; + } +} + +Macro::Opcode MacroJITx64Impl::GetOpCode() const { + ASSERT(pc < code.size()); + return {code[pc]}; +} +} // Anonymous namespace +#endif + +static void Dump(u64 hash, std::span code, bool decompiled = false) { + const auto base_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; + const auto macro_dir{base_dir / "macros"}; + if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { + LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); + return; + } + auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; + + if (decompiled) { + auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)}; + if (Common::FS::Exists(name)) { + (void)Common::FS::RenameFile(name, new_name); + return; + } + name = new_name; + } + + std::fstream macro_file(name, std::ios::out | std::ios::binary); + if (!macro_file) { + LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", Common::FS::PathToUTF8String(name)); + return; + } + macro_file.write(reinterpret_cast(code.data()), code.size_bytes()); +} + +MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d_, bool is_interpreted_) + : hle_macros{std::make_optional(maxwell3d_)} + , maxwell3d{maxwell3d_} + , is_interpreted{is_interpreted_} +{} + +MacroEngine::~MacroEngine() = default; + +void MacroEngine::AddCode(u32 method, u32 data) { + uploaded_macro_code[method].push_back(data); +} + +void MacroEngine::ClearCode(u32 method) { + macro_cache.erase(method); + uploaded_macro_code.erase(method); +} + +void MacroEngine::Execute(u32 method, const std::vector& parameters) { + auto compiled_macro = macro_cache.find(method); + if (compiled_macro != macro_cache.end()) { + const auto& cache_info = compiled_macro->second; + if (cache_info.has_hle_program) { + cache_info.hle_program->Execute(parameters, method); + } else { + maxwell3d.RefreshParameters(); + cache_info.lle_program->Execute(parameters, method); + } + } else { + // Macro not compiled, check if it's uploaded and if so, compile it + std::optional mid_method; + const auto macro_code = uploaded_macro_code.find(method); + if (macro_code == uploaded_macro_code.end()) { + for (const auto& [method_base, code] : uploaded_macro_code) { + if (method >= method_base && (method - method_base) < code.size()) { + mid_method = method_base; + break; + } + } + if (!mid_method.has_value()) { + ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method); + return; + } + } + auto& cache_info = macro_cache[method]; + + if (!mid_method.has_value()) { + cache_info.lle_program = Compile(macro_code->second); + cache_info.hash = Common::HashValue(macro_code->second); + } else { + const auto& macro_cached = uploaded_macro_code[mid_method.value()]; + const auto rebased_method = method - mid_method.value(); + auto& code = uploaded_macro_code[method]; + code.resize(macro_cached.size() - rebased_method); + std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32)); + cache_info.hash = Common::HashValue(code); + cache_info.lle_program = Compile(code); + } + + auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); + if (!hle_program || Settings::values.disable_macro_hle) { + maxwell3d.RefreshParameters(); + cache_info.lle_program->Execute(parameters, method); + } else { + cache_info.has_hle_program = true; + cache_info.hle_program = std::move(hle_program); + cache_info.hle_program->Execute(parameters, method); + } + + if (Settings::values.dump_macros) { + Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); + } + } +} + +std::unique_ptr MacroEngine::Compile(const std::vector& code) { +#ifdef ARCHITECTURE_x86_64 + if (!is_interpreted) + return std::make_unique(maxwell3d, code); +#endif + return std::make_unique(maxwell3d, code); +} + +std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d) { +#ifdef ARCHITECTURE_x86_64 + return std::make_optional(maxwell3d, bool(Settings::values.disable_macro_jit)); +#else + return std::make_optional(maxwell3d, true); +#endif +} + +} // namespace Tegra diff --git a/src/video_core/macro/macro.h b/src/video_core/macro.h similarity index 74% rename from src/video_core/macro/macro.h rename to src/video_core/macro.h index 737ced9a45..685097a693 100644 --- a/src/video_core/macro/macro.h +++ b/src/video_core/macro.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -95,24 +98,34 @@ union MethodAddress { } // namespace Macro -class HLEMacro; - class CachedMacro { public: + CachedMacro(Engines::Maxwell3D& maxwell3d_) + : maxwell3d{maxwell3d_} + {} virtual ~CachedMacro() = default; - /** - * Executes the macro code with the specified input parameters. - * - * @param parameters The parameters of the macro - * @param method The method to execute - */ + /// Executes the macro code with the specified input parameters. + /// @param parameters The parameters of the macro + /// @param method The method to execute virtual void Execute(const std::vector& parameters, u32 method) = 0; + Engines::Maxwell3D& maxwell3d; +}; + +class HLEMacro { +public: + explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); + ~HLEMacro(); + // Allocates and returns a cached macro if the hash matches a known function. + // Returns nullptr otherwise. + [[nodiscard]] std::unique_ptr GetHLEProgram(u64 hash) const; +private: + Engines::Maxwell3D& maxwell3d; }; class MacroEngine { public: - explicit MacroEngine(Engines::Maxwell3D& maxwell3d); - virtual ~MacroEngine(); + explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted); + ~MacroEngine(); // Store the uploaded macro code to compile them when they're called. void AddCode(u32 method, u32 data); @@ -124,7 +137,7 @@ public: void Execute(u32 method, const std::vector& parameters); protected: - virtual std::unique_ptr Compile(const std::vector& code) = 0; + std::unique_ptr Compile(const std::vector& code); private: struct CacheInfo { @@ -136,10 +149,11 @@ private: std::unordered_map macro_cache; std::unordered_map> uploaded_macro_code; - std::unique_ptr hle_macros; + std::optional hle_macros; Engines::Maxwell3D& maxwell3d; + bool is_interpreted; }; -std::unique_ptr GetMacroEngine(Engines::Maxwell3D& maxwell3d); +std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d); } // namespace Tegra diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp deleted file mode 100644 index 2ff5e21c5e..0000000000 --- a/src/video_core/macro/macro.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include "common/container_hash.h" - -#include -#include "common/assert.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/settings.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/macro/macro.h" -#include "video_core/macro/macro_hle.h" -#include "video_core/macro/macro_interpreter.h" - -#ifdef ARCHITECTURE_x86_64 -#include "video_core/macro/macro_jit_x64.h" -#endif - -namespace Tegra { - -static void Dump(u64 hash, std::span code, bool decompiled = false) { - const auto base_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; - const auto macro_dir{base_dir / "macros"}; - if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { - LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); - return; - } - auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; - - if (decompiled) { - auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)}; - if (Common::FS::Exists(name)) { - (void)Common::FS::RenameFile(name, new_name); - return; - } - name = new_name; - } - - std::fstream macro_file(name, std::ios::out | std::ios::binary); - if (!macro_file) { - LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", - Common::FS::PathToUTF8String(name)); - return; - } - macro_file.write(reinterpret_cast(code.data()), code.size_bytes()); -} - -MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d_) - : hle_macros{std::make_unique(maxwell3d_)}, maxwell3d{maxwell3d_} {} - -MacroEngine::~MacroEngine() = default; - -void MacroEngine::AddCode(u32 method, u32 data) { - uploaded_macro_code[method].push_back(data); -} - -void MacroEngine::ClearCode(u32 method) { - macro_cache.erase(method); - uploaded_macro_code.erase(method); -} - -void MacroEngine::Execute(u32 method, const std::vector& parameters) { - auto compiled_macro = macro_cache.find(method); - if (compiled_macro != macro_cache.end()) { - const auto& cache_info = compiled_macro->second; - if (cache_info.has_hle_program) { - cache_info.hle_program->Execute(parameters, method); - } else { - maxwell3d.RefreshParameters(); - cache_info.lle_program->Execute(parameters, method); - } - } else { - // Macro not compiled, check if it's uploaded and if so, compile it - std::optional mid_method; - const auto macro_code = uploaded_macro_code.find(method); - if (macro_code == uploaded_macro_code.end()) { - for (const auto& [method_base, code] : uploaded_macro_code) { - if (method >= method_base && (method - method_base) < code.size()) { - mid_method = method_base; - break; - } - } - if (!mid_method.has_value()) { - ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method); - return; - } - } - auto& cache_info = macro_cache[method]; - - if (!mid_method.has_value()) { - cache_info.lle_program = Compile(macro_code->second); - cache_info.hash = Common::HashValue(macro_code->second); - } else { - const auto& macro_cached = uploaded_macro_code[mid_method.value()]; - const auto rebased_method = method - mid_method.value(); - auto& code = uploaded_macro_code[method]; - code.resize(macro_cached.size() - rebased_method); - std::memcpy(code.data(), macro_cached.data() + rebased_method, - code.size() * sizeof(u32)); - cache_info.hash = Common::HashValue(code); - cache_info.lle_program = Compile(code); - } - - auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); - if (!hle_program || Settings::values.disable_macro_hle) { - maxwell3d.RefreshParameters(); - cache_info.lle_program->Execute(parameters, method); - } else { - cache_info.has_hle_program = true; - cache_info.hle_program = std::move(hle_program); - cache_info.hle_program->Execute(parameters, method); - } - - if (Settings::values.dump_macros) { - Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); - } - } -} - -std::unique_ptr GetMacroEngine(Engines::Maxwell3D& maxwell3d) { - if (Settings::values.disable_macro_jit) { - return std::make_unique(maxwell3d); - } -#ifdef ARCHITECTURE_x86_64 - return std::make_unique(maxwell3d); -#else - return std::make_unique(maxwell3d); -#endif -} - -} // namespace Tegra diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp deleted file mode 100644 index 2f41e806c2..0000000000 --- a/src/video_core/macro/macro_hle.cpp +++ /dev/null @@ -1,606 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include "common/assert.h" -#include "common/scope_exit.h" -#include "video_core/dirty_flags.h" -#include "video_core/engines/draw_manager.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/macro/macro.h" -#include "video_core/macro/macro_hle.h" -#include "video_core/memory_manager.h" -#include "video_core/rasterizer_interface.h" - -namespace Tegra { - -using Maxwell3D = Engines::Maxwell3D; - -namespace { - -bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { - switch (topology) { - case Maxwell3D::Regs::PrimitiveTopology::Points: - case Maxwell3D::Regs::PrimitiveTopology::Lines: - case Maxwell3D::Regs::PrimitiveTopology::LineLoop: - case Maxwell3D::Regs::PrimitiveTopology::LineStrip: - case Maxwell3D::Regs::PrimitiveTopology::Triangles: - case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip: - case Maxwell3D::Regs::PrimitiveTopology::TriangleFan: - case Maxwell3D::Regs::PrimitiveTopology::LinesAdjacency: - case Maxwell3D::Regs::PrimitiveTopology::LineStripAdjacency: - case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency: - case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency: - case Maxwell3D::Regs::PrimitiveTopology::Patches: - return true; - case Maxwell3D::Regs::PrimitiveTopology::Quads: - case Maxwell3D::Regs::PrimitiveTopology::QuadStrip: - case Maxwell3D::Regs::PrimitiveTopology::Polygon: - default: - return false; - } -} - -class HLEMacroImpl : public CachedMacro { -public: - explicit HLEMacroImpl(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} - -protected: - Maxwell3D& maxwell3d; -}; - -/* - * @note: these macros have two versions, a normal and extended version, with the extended version - * also assigning the base vertex/instance. - */ -template -class HLE_DrawArraysIndirect final : public HLEMacroImpl { -public: - explicit HLE_DrawArraysIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 4 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; - - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawArrayIndirect(topology); - - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } - -private: - void Fallback(const std::vector& parameters) { - SCOPE_EXIT { - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - }; - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); - - auto topology = static_cast(parameters[0]); - const u32 vertex_first = parameters[3]; - const u32 vertex_count = parameters[1]; - - if (!IsTopologySafe(topology) && - static_cast(maxwell3d.GetMaxCurrentVertices()) < - static_cast(vertex_first) + static_cast(vertex_count)) { - ASSERT_MSG(false, "Faulty draw!"); - return; - } - - const u32 base_instance = parameters[4]; - if constexpr (extended) { - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, - instance_count); - - if constexpr (extended) { - maxwell3d.regs.global_base_instance_index = 0; - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } -}; - -/* - * @note: these macros have two versions, a normal and extended version, with the extended version - * also assigning the base vertex/instance. - */ -template -class HLE_DrawIndexedIndirect final : public HLEMacroImpl { -public: - explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 5 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } - -private: - void Fallback(const std::vector& parameters) { - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawIndex( - static_cast(parameters[0]), parameters[3], - parameters[1], element_base, base_instance, instance_count); - - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if constexpr (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } -}; - -class HLE_MultiLayerClear final : public HLEMacroImpl { -public: - explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - ASSERT(parameters.size() == 1); - - const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; - const u32 rt_index = clear_params.RT; - const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; - ASSERT(clear_params.layer == 0); - - maxwell3d.regs.clear_surface.raw = clear_params.raw; - maxwell3d.draw_manager->Clear(num_layers); - } -}; - -class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl { -public: - explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - const auto topology = static_cast(parameters[2]); - if (!IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - - const u32 padding = parameters[3]; // padding is in words - - // size of each indirect segment - const u32 indirect_words = 5 + padding; - const u32 stride = indirect_words * sizeof(u32); - const std::size_t draw_count = end_indirect - start_indirect; - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = true; - params.count_start_address = maxwell3d.GetMacroAddress(4); - params.indirect_start_address = maxwell3d.GetMacroAddress(5); - params.buffer_size = stride * draw_count; - params.max_draw_counts = draw_count; - params.stride = stride; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.SetHLEReplacementAttributeType(0, 0x648, - Maxwell3D::HLEReplacementAttributeType::DrawID); - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - -private: - void Fallback(const std::vector& parameters) { - SCOPE_EXIT { - // Clean everything. - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - }; - maxwell3d.RefreshParameters(); - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - const auto topology = static_cast(parameters[2]); - const u32 padding = parameters[3]; - const std::size_t max_draws = parameters[4]; - - const u32 indirect_words = 5 + padding; - const std::size_t first_draw = start_indirect; - const std::size_t effective_draws = end_indirect - start_indirect; - const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); - - for (std::size_t index = first_draw; index < last_draw; index++) { - const std::size_t base = index * indirect_words + 5; - const u32 base_vertex = parameters[base + 3]; - const u32 base_instance = parameters[base + 4]; - maxwell3d.regs.vertex_id_base = base_vertex; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.CallMethod(0x8e3, 0x648, true); - maxwell3d.CallMethod(0x8e4, static_cast(index), true); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], - base_vertex, base_instance, parameters[base + 1]); - } - } -}; - -class HLE_DrawIndirectByteCount final : public HLEMacroImpl { -public: - explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); - - auto topology = static_cast(parameters[0] & 0xFFFFU); - if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { - Fallback(parameters); - return; - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = true; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(2); - params.buffer_size = 4; - params.max_draw_counts = 1; - params.stride = parameters[1]; - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - - maxwell3d.draw_manager->DrawArrayIndirect(topology); - } - -private: - void Fallback(const std::vector& parameters) { - maxwell3d.RefreshParameters(); - - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - - maxwell3d.draw_manager->DrawArray( - maxwell3d.regs.draw.topology, 0, - maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); - } -}; - -class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { -public: - explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; - const u32 address = maxwell3d.regs.shadow_scratch[24]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = 0x7000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - const_buffer.offset = offset; - } -}; - -class HLE_D7333D26E0A93EDE final : public HLEMacroImpl { -public: - explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const size_t index = parameters[0]; - const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; - const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = size; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - } -}; - -class HLE_BindShader final : public HLEMacroImpl { -public: - explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - auto& regs = maxwell3d.regs; - const u32 index = parameters[0]; - if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { - return; - } - - regs.pipelines[index & 0xF].offset = parameters[2]; - maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; - regs.shadow_scratch[28 + index] = parameters[1]; - regs.shadow_scratch[34 + index] = parameters[2]; - - const u32 address = parameters[4]; - auto& const_buffer = regs.const_buffer; - const_buffer.size = 0x10000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - - const size_t bind_group_id = parameters[3] & 0x7F; - auto& bind_group = regs.bind_groups[bind_group_id]; - bind_group.raw_config = 0x11; - maxwell3d.ProcessCBBind(bind_group_id); - } -}; - -class HLE_SetRasterBoundingBox final : public HLEMacroImpl { -public: - explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const u32 raster_mode = parameters[0]; - auto& regs = maxwell3d.regs; - const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; - const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; - regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; - regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); - } -}; - -template -class HLE_ClearConstBuffer final : public HLEMacroImpl { -public: - explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - static constexpr std::array zeroes{}; - auto& regs = maxwell3d.regs; - regs.const_buffer.size = static_cast(base_size); - regs.const_buffer.address_high = parameters[0]; - regs.const_buffer.address_low = parameters[1]; - regs.const_buffer.offset = 0; - maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); - } -}; - -class HLE_ClearMemory final : public HLEMacroImpl { -public: - explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - - const u32 needed_memory = parameters[2] / sizeof(u32); - if (needed_memory > zero_memory.size()) { - zero_memory.resize(needed_memory, 0); - } - auto& regs = maxwell3d.regs; - regs.upload.line_length_in = parameters[2]; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(static_cast(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMultiMethod(static_cast(MAXWELL3D_REG_INDEX(inline_data)), - zero_memory.data(), needed_memory, needed_memory); - } - -private: - std::vector zero_memory; -}; - -class HLE_TransformFeedbackSetup final : public HLEMacroImpl { -public: - explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - - auto& regs = maxwell3d.regs; - regs.transform_feedback_enabled = 1; - regs.transform_feedback.buffers[0].start_offset = 0; - regs.transform_feedback.buffers[1].start_offset = 0; - regs.transform_feedback.buffers[2].start_offset = 0; - regs.transform_feedback.buffers[3].start_offset = 0; - - regs.upload.line_length_in = 4; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(static_cast(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMethod(static_cast(MAXWELL3D_REG_INDEX(inline_data)), - regs.transform_feedback.controls[0].stride, true); - - maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); - } -}; - -} // Anonymous namespace - -HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} { - builders.emplace(0x0D61FC9FAAC9FCADULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0x8A4D173EB99A8603ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0x771BB18C62444DA0ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0x0217920100488FF7ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0x3F5E74B9C9A50164ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique( - maxwell3d__); - })); - builders.emplace(0xEAD26C3E2109B06BULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xC713C83D8F63CCF3ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xD7333D26E0A93EDEULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xEB29B2A09AA06D38ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xDB1341DBEB4C8AF7ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0x6C97861D891EDf7EULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0xD246FDDF3A6173D7ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique>(maxwell3d__); - })); - builders.emplace(0xEE4D0004BEC8ECF4ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xFC0CF27F5FFAA661ULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); - builders.emplace(0xB5F74EDB717278ECULL, - std::function(Maxwell3D&)>( - [](Maxwell3D& maxwell3d__) -> std::unique_ptr { - return std::make_unique(maxwell3d__); - })); -} - -HLEMacro::~HLEMacro() = default; - -std::unique_ptr HLEMacro::GetHLEProgram(u64 hash) const { - const auto it = builders.find(hash); - if (it == builders.end()) { - return nullptr; - } - return it->second(maxwell3d); -} - -} // namespace Tegra diff --git a/src/video_core/macro/macro_hle.h b/src/video_core/macro/macro_hle.h deleted file mode 100644 index 33f92fab16..0000000000 --- a/src/video_core/macro/macro_hle.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "common/common_types.h" - -namespace Tegra { - -namespace Engines { -class Maxwell3D; -} - -class HLEMacro { -public: - explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); - ~HLEMacro(); - - // Allocates and returns a cached macro if the hash matches a known function. - // Returns nullptr otherwise. - [[nodiscard]] std::unique_ptr GetHLEProgram(u64 hash) const; - -private: - Engines::Maxwell3D& maxwell3d; - std::unordered_map(Engines::Maxwell3D&)>> - builders; -}; - -} // namespace Tegra diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp deleted file mode 100644 index f9befce676..0000000000 --- a/src/video_core/macro/macro_interpreter.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#include "common/assert.h" -#include "common/logging/log.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/macro/macro_interpreter.h" - -namespace Tegra { -namespace { -class MacroInterpreterImpl final : public CachedMacro { -public: - explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) - : maxwell3d{maxwell3d_}, code{code_} {} - - void Execute(const std::vector& params, u32 method) override; - -private: - /// Resets the execution engine state, zeroing registers, etc. - void Reset(); - - /** - * Executes a single macro instruction located at the current program counter. Returns whether - * the interpreter should keep running. - * - * @param is_delay_slot Whether the current step is being executed due to a delay slot in a - * previous instruction. - */ - bool Step(bool is_delay_slot); - - /// Calculates the result of an ALU operation. src_a OP src_b; - u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); - - /// Performs the result operation on the input result and stores it in the specified register - /// (if necessary). - void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result); - - /// Evaluates the branch condition and returns whether the branch should be taken or not. - bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; - - /// Reads an opcode at the current program counter location. - Macro::Opcode GetOpcode() const; - - /// Returns the specified register's value. Register 0 is hardcoded to always return 0. - u32 GetRegister(u32 register_id) const; - - /// Sets the register to the input value. - void SetRegister(u32 register_id, u32 value); - - /// Sets the method address to use for the next Send instruction. - void SetMethodAddress(u32 address); - - /// Calls a GPU Engine method with the input parameter. - void Send(u32 value); - - /// Reads a GPU register located at the method address. - u32 Read(u32 method) const; - - /// Returns the next parameter in the parameter queue. - u32 FetchParameter(); - - Engines::Maxwell3D& maxwell3d; - - /// Current program counter - u32 pc{}; - /// Program counter to execute at after the delay slot is executed. - std::optional delayed_pc; - - /// General purpose macro registers. - std::array registers = {}; - - /// Method address to use for the next Send instruction. - Macro::MethodAddress method_address = {}; - - /// Input parameters of the current macro. - std::unique_ptr parameters; - std::size_t num_parameters = 0; - std::size_t parameters_capacity = 0; - /// Index of the next parameter that will be fetched by the 'parm' instruction. - u32 next_parameter_index = 0; - - bool carry_flag = false; - const std::vector& code; -}; - -void MacroInterpreterImpl::Execute(const std::vector& params, u32 method) { - Reset(); - - registers[1] = params[0]; - num_parameters = params.size(); - - if (num_parameters > parameters_capacity) { - parameters_capacity = num_parameters; - parameters = std::make_unique(num_parameters); - } - std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32)); - - // Execute the code until we hit an exit condition. - bool keep_executing = true; - while (keep_executing) { - keep_executing = Step(false); - } - - // Assert the the macro used all the input parameters - ASSERT(next_parameter_index == num_parameters); -} - -void MacroInterpreterImpl::Reset() { - registers = {}; - pc = 0; - delayed_pc = {}; - method_address.raw = 0; - num_parameters = 0; - // The next parameter index starts at 1, because $r1 already has the value of the first - // parameter. - next_parameter_index = 1; - carry_flag = false; -} - -bool MacroInterpreterImpl::Step(bool is_delay_slot) { - u32 base_address = pc; - - Macro::Opcode opcode = GetOpcode(); - pc += 4; - - // Update the program counter if we were delayed - if (delayed_pc) { - ASSERT(is_delay_slot); - pc = *delayed_pc; - delayed_pc = {}; - } - - switch (opcode.operation) { - case Macro::Operation::ALU: { - u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), - GetRegister(opcode.src_b)); - ProcessResult(opcode.result_operation, opcode.dst, result); - break; - } - case Macro::Operation::AddImmediate: { - ProcessResult(opcode.result_operation, opcode.dst, - GetRegister(opcode.src_a) + opcode.immediate); - break; - } - case Macro::Operation::ExtractInsert: { - u32 dst = GetRegister(opcode.src_a); - u32 src = GetRegister(opcode.src_b); - - src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); - dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); - dst |= src << opcode.bf_dst_bit; - ProcessResult(opcode.result_operation, opcode.dst, dst); - break; - } - case Macro::Operation::ExtractShiftLeftImmediate: { - u32 dst = GetRegister(opcode.src_a); - u32 src = GetRegister(opcode.src_b); - - u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; - - ProcessResult(opcode.result_operation, opcode.dst, result); - break; - } - case Macro::Operation::ExtractShiftLeftRegister: { - u32 dst = GetRegister(opcode.src_a); - u32 src = GetRegister(opcode.src_b); - - u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; - - ProcessResult(opcode.result_operation, opcode.dst, result); - break; - } - case Macro::Operation::Read: { - u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); - ProcessResult(opcode.result_operation, opcode.dst, result); - break; - } - case Macro::Operation::Branch: { - ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); - u32 value = GetRegister(opcode.src_a); - bool taken = EvaluateBranchCondition(opcode.branch_condition, value); - if (taken) { - // Ignore the delay slot if the branch has the annul bit. - if (opcode.branch_annul) { - pc = base_address + opcode.GetBranchTarget(); - return true; - } - - delayed_pc = base_address + opcode.GetBranchTarget(); - // Execute one more instruction due to the delay slot. - return Step(true); - } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value()); - break; - } - - // An instruction with the Exit flag will not actually - // cause an exit if it's executed inside a delay slot. - if (opcode.is_exit && !is_delay_slot) { - // Exit has a delay slot, execute the next instruction - Step(true); - return false; - } - - return true; -} - -u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) { - switch (operation) { - case Macro::ALUOperation::Add: { - const u64 result{static_cast(src_a) + src_b}; - carry_flag = result > 0xffffffff; - return static_cast(result); - } - case Macro::ALUOperation::AddWithCarry: { - const u64 result{static_cast(src_a) + src_b + (carry_flag ? 1ULL : 0ULL)}; - carry_flag = result > 0xffffffff; - return static_cast(result); - } - case Macro::ALUOperation::Subtract: { - const u64 result{static_cast(src_a) - src_b}; - carry_flag = result < 0x100000000; - return static_cast(result); - } - case Macro::ALUOperation::SubtractWithBorrow: { - const u64 result{static_cast(src_a) - src_b - (carry_flag ? 0ULL : 1ULL)}; - carry_flag = result < 0x100000000; - return static_cast(result); - } - case Macro::ALUOperation::Xor: - return src_a ^ src_b; - case Macro::ALUOperation::Or: - return src_a | src_b; - case Macro::ALUOperation::And: - return src_a & src_b; - case Macro::ALUOperation::AndNot: - return src_a & ~src_b; - case Macro::ALUOperation::Nand: - return ~(src_a & src_b); - - default: - UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", operation); - return 0; - } -} - -void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) { - switch (operation) { - case Macro::ResultOperation::IgnoreAndFetch: - // Fetch parameter and ignore result. - SetRegister(reg, FetchParameter()); - break; - case Macro::ResultOperation::Move: - // Move result. - SetRegister(reg, result); - break; - case Macro::ResultOperation::MoveAndSetMethod: - // Move result and use as Method Address. - SetRegister(reg, result); - SetMethodAddress(result); - break; - case Macro::ResultOperation::FetchAndSend: - // Fetch parameter and send result. - SetRegister(reg, FetchParameter()); - Send(result); - break; - case Macro::ResultOperation::MoveAndSend: - // Move and send result. - SetRegister(reg, result); - Send(result); - break; - case Macro::ResultOperation::FetchAndSetMethod: - // Fetch parameter and use result as Method Address. - SetRegister(reg, FetchParameter()); - SetMethodAddress(result); - break; - case Macro::ResultOperation::MoveAndSetMethodFetchAndSend: - // Move result and use as Method Address, then fetch and send parameter. - SetRegister(reg, result); - SetMethodAddress(result); - Send(FetchParameter()); - break; - case Macro::ResultOperation::MoveAndSetMethodSend: - // Move result and use as Method Address, then send bits 12:17 of result. - SetRegister(reg, result); - SetMethodAddress(result); - Send((result >> 12) & 0b111111); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); - break; - } -} - -bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const { - switch (cond) { - case Macro::BranchCondition::Zero: - return value == 0; - case Macro::BranchCondition::NotZero: - return value != 0; - } - UNREACHABLE(); -} - -Macro::Opcode MacroInterpreterImpl::GetOpcode() const { - ASSERT((pc % sizeof(u32)) == 0); - ASSERT(pc < code.size() * sizeof(u32)); - return {code[pc / sizeof(u32)]}; -} - -u32 MacroInterpreterImpl::GetRegister(u32 register_id) const { - return registers.at(register_id); -} - -void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { - // Register 0 is hardwired as the zero register. - // Ensure no writes to it actually occur. - if (register_id == 0) { - return; - } - - registers.at(register_id) = value; -} - -void MacroInterpreterImpl::SetMethodAddress(u32 address) { - method_address.raw = address; -} - -void MacroInterpreterImpl::Send(u32 value) { - maxwell3d.CallMethod(method_address.address, value, true); - // Increment the method address by the method increment. - method_address.address.Assign(method_address.address.Value() + - method_address.increment.Value()); -} - -u32 MacroInterpreterImpl::Read(u32 method) const { - return maxwell3d.GetRegisterValue(method); -} - -u32 MacroInterpreterImpl::FetchParameter() { - ASSERT(next_parameter_index < num_parameters); - return parameters[next_parameter_index++]; -} -} // Anonymous namespace - -MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d_) - : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {} - -std::unique_ptr MacroInterpreter::Compile(const std::vector& code) { - return std::make_unique(maxwell3d, code); -} - -} // namespace Tegra diff --git a/src/video_core/macro/macro_interpreter.h b/src/video_core/macro/macro_interpreter.h deleted file mode 100644 index f5eeb0b76f..0000000000 --- a/src/video_core/macro/macro_interpreter.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/common_types.h" -#include "video_core/macro/macro.h" - -namespace Tegra { -namespace Engines { -class Maxwell3D; -} - -class MacroInterpreter final : public MacroEngine { -public: - explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d_); - -protected: - std::unique_ptr Compile(const std::vector& code) override; - -private: - Engines::Maxwell3D& maxwell3d; -}; - -} // namespace Tegra diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp deleted file mode 100644 index 65935f6c62..0000000000 --- a/src/video_core/macro/macro_jit_x64.cpp +++ /dev/null @@ -1,678 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include - -#include "common/assert.h" -#include "common/bit_field.h" -#include "common/logging/log.h" -#include "common/x64/xbyak_abi.h" -#include "common/x64/xbyak_util.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/macro/macro_interpreter.h" -#include "video_core/macro/macro_jit_x64.h" - -namespace Tegra { -namespace { -constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx; -constexpr Xbyak::Reg32 RESULT = Xbyak::util::r10d; -constexpr Xbyak::Reg64 MAX_PARAMETER = Xbyak::util::r11; -constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12; -constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d; -constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; - -constexpr std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({ - STATE, - RESULT, - MAX_PARAMETER, - PARAMETERS, - METHOD_ADDRESS, - BRANCH_HOLDER, -}); - -// Arbitrarily chosen based on current booting games. -constexpr size_t MAX_CODE_SIZE = 0x10000; - -std::bitset<32> PersistentCallerSavedRegs() { - return PERSISTENT_REGISTERS & Common::X64::ABI_ALL_CALLER_SAVED; -} - -/// @brief Must enforce W^X constraints, as we yet don't havea global "NO_EXECUTE" support flag -/// the speed loss is minimal, and in fact may be negligible, however for your peace of mind -/// I simply included known OSes whom had W^X issues -#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) -static const auto default_cg_mode = Xbyak::DontSetProtectRWE; -#else -static const auto default_cg_mode = nullptr; //Allow RWE -#endif - -class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { -public: - explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) - : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) - , code{code_}, maxwell3d{maxwell3d_} { - Compile(); - } - - void Execute(const std::vector& parameters, u32 method) override; - - void Compile_ALU(Macro::Opcode opcode); - void Compile_AddImmediate(Macro::Opcode opcode); - void Compile_ExtractInsert(Macro::Opcode opcode); - void Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode); - void Compile_ExtractShiftLeftRegister(Macro::Opcode opcode); - void Compile_Read(Macro::Opcode opcode); - void Compile_Branch(Macro::Opcode opcode); - -private: - void Optimizer_ScanFlags(); - - void Compile(); - bool Compile_NextInstruction(); - - Xbyak::Reg32 Compile_FetchParameter(); - Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); - - void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); - void Compile_Send(Xbyak::Reg32 value); - - Macro::Opcode GetOpCode() const; - - struct JITState { - Engines::Maxwell3D* maxwell3d{}; - std::array registers{}; - u32 carry_flag{}; - }; - static_assert(offsetof(JITState, maxwell3d) == 0, "Maxwell3D is not at 0x0"); - using ProgramType = void (*)(JITState*, const u32*, const u32*); - - struct OptimizerState { - bool can_skip_carry{}; - bool has_delayed_pc{}; - bool zero_reg_skip{}; - bool skip_dummy_addimmediate{}; - bool optimize_for_method_move{}; - bool enable_asserts{}; - }; - OptimizerState optimizer{}; - - std::optional next_opcode{}; - ProgramType program{nullptr}; - - std::array labels; - std::array delay_skip; - Xbyak::Label end_of_code{}; - - bool is_delay_slot{}; - u32 pc{}; - - const std::vector& code; - Engines::Maxwell3D& maxwell3d; -}; - -void MacroJITx64Impl::Execute(const std::vector& parameters, u32 method) { - ASSERT_OR_EXECUTE(program != nullptr, { return; }); - JITState state{}; - state.maxwell3d = &maxwell3d; - state.registers = {}; - program(&state, parameters.data(), parameters.data() + parameters.size()); -} - -void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { - const bool is_a_zero = opcode.src_a == 0; - const bool is_b_zero = opcode.src_b == 0; - const bool valid_operation = !is_a_zero && !is_b_zero; - [[maybe_unused]] const bool is_move_operation = !is_a_zero && is_b_zero; - const bool has_zero_register = is_a_zero || is_b_zero; - const bool no_zero_reg_skip = opcode.alu_operation == Macro::ALUOperation::AddWithCarry || - opcode.alu_operation == Macro::ALUOperation::SubtractWithBorrow; - - Xbyak::Reg32 src_a; - Xbyak::Reg32 src_b; - - if (!optimizer.zero_reg_skip || no_zero_reg_skip) { - src_a = Compile_GetRegister(opcode.src_a, RESULT); - src_b = Compile_GetRegister(opcode.src_b, eax); - } else { - if (!is_a_zero) { - src_a = Compile_GetRegister(opcode.src_a, RESULT); - } - if (!is_b_zero) { - src_b = Compile_GetRegister(opcode.src_b, eax); - } - } - - bool has_emitted = false; - - switch (opcode.alu_operation) { - case Macro::ALUOperation::Add: - if (optimizer.zero_reg_skip) { - if (valid_operation) { - add(src_a, src_b); - } - } else { - add(src_a, src_b); - } - - if (!optimizer.can_skip_carry) { - setc(byte[STATE + offsetof(JITState, carry_flag)]); - } - break; - case Macro::ALUOperation::AddWithCarry: - bt(dword[STATE + offsetof(JITState, carry_flag)], 0); - adc(src_a, src_b); - setc(byte[STATE + offsetof(JITState, carry_flag)]); - break; - case Macro::ALUOperation::Subtract: - if (optimizer.zero_reg_skip) { - if (valid_operation) { - sub(src_a, src_b); - has_emitted = true; - } - } else { - sub(src_a, src_b); - has_emitted = true; - } - if (!optimizer.can_skip_carry && has_emitted) { - setc(byte[STATE + offsetof(JITState, carry_flag)]); - } - break; - case Macro::ALUOperation::SubtractWithBorrow: - bt(dword[STATE + offsetof(JITState, carry_flag)], 0); - sbb(src_a, src_b); - setc(byte[STATE + offsetof(JITState, carry_flag)]); - break; - case Macro::ALUOperation::Xor: - if (optimizer.zero_reg_skip) { - if (valid_operation) { - xor_(src_a, src_b); - } - } else { - xor_(src_a, src_b); - } - break; - case Macro::ALUOperation::Or: - if (optimizer.zero_reg_skip) { - if (valid_operation) { - or_(src_a, src_b); - } - } else { - or_(src_a, src_b); - } - break; - case Macro::ALUOperation::And: - if (optimizer.zero_reg_skip) { - if (!has_zero_register) { - and_(src_a, src_b); - } - } else { - and_(src_a, src_b); - } - break; - case Macro::ALUOperation::AndNot: - if (optimizer.zero_reg_skip) { - if (!is_a_zero) { - not_(src_b); - and_(src_a, src_b); - } - } else { - not_(src_b); - and_(src_a, src_b); - } - break; - case Macro::ALUOperation::Nand: - if (optimizer.zero_reg_skip) { - if (!is_a_zero) { - and_(src_a, src_b); - not_(src_a); - } - } else { - and_(src_a, src_b); - not_(src_a); - } - break; - default: - UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value()); - break; - } - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) { - if (optimizer.skip_dummy_addimmediate) { - // Games tend to use this as an exit instruction placeholder. It's to encode an instruction - // without doing anything. In our case we can just not emit anything. - if (opcode.result_operation == Macro::ResultOperation::Move && opcode.dst == 0) { - return; - } - } - // Check for redundant moves - if (optimizer.optimize_for_method_move && - opcode.result_operation == Macro::ResultOperation::MoveAndSetMethod) { - if (next_opcode.has_value()) { - const auto next = *next_opcode; - if (next.result_operation == Macro::ResultOperation::MoveAndSetMethod && - opcode.dst == next.dst) { - return; - } - } - } - if (optimizer.zero_reg_skip && opcode.src_a == 0) { - if (opcode.immediate == 0) { - xor_(RESULT, RESULT); - } else { - mov(RESULT, opcode.immediate); - } - } else { - auto result = Compile_GetRegister(opcode.src_a, RESULT); - if (opcode.immediate > 2) { - add(result, opcode.immediate); - } else if (opcode.immediate == 1) { - inc(result); - } else if (opcode.immediate < 0) { - sub(result, opcode.immediate * -1); - } - } - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) { - auto dst = Compile_GetRegister(opcode.src_a, RESULT); - auto src = Compile_GetRegister(opcode.src_b, eax); - - const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); - and_(dst, mask); - shr(src, opcode.bf_src_bit); - and_(src, opcode.GetBitfieldMask()); - shl(src, opcode.bf_dst_bit); - or_(dst, src); - - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) { - const auto dst = Compile_GetRegister(opcode.src_a, ecx); - const auto src = Compile_GetRegister(opcode.src_b, RESULT); - - shr(src, dst.cvt8()); - and_(src, opcode.GetBitfieldMask()); - shl(src, opcode.bf_dst_bit); - - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) { - const auto dst = Compile_GetRegister(opcode.src_a, ecx); - const auto src = Compile_GetRegister(opcode.src_b, RESULT); - - shr(src, opcode.bf_src_bit); - and_(src, opcode.GetBitfieldMask()); - shl(src, dst.cvt8()); - - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { - if (optimizer.zero_reg_skip && opcode.src_a == 0) { - if (opcode.immediate == 0) { - xor_(RESULT, RESULT); - } else { - mov(RESULT, opcode.immediate); - } - } else { - auto result = Compile_GetRegister(opcode.src_a, RESULT); - if (opcode.immediate > 2) { - add(result, opcode.immediate); - } else if (opcode.immediate == 1) { - inc(result); - } else if (opcode.immediate < 0) { - sub(result, opcode.immediate * -1); - } - } - - // Equivalent to Engines::Maxwell3D::GetRegisterValue: - if (optimizer.enable_asserts) { - Xbyak::Label pass_range_check; - cmp(RESULT, static_cast(Engines::Maxwell3D::Regs::NUM_REGS)); - jb(pass_range_check); - int3(); - L(pass_range_check); - } - mov(rax, qword[STATE]); - mov(RESULT, - dword[rax + offsetof(Engines::Maxwell3D, regs) + - offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]); - - Compile_ProcessResult(opcode.result_operation, opcode.dst); -} - -void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { - maxwell3d->CallMethod(method_address.address, value, true); -} - -void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { - Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - mov(Common::X64::ABI_PARAM1, qword[STATE]); - mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); - mov(Common::X64::ABI_PARAM3.cvt32(), value); - Common::X64::CallFarFunction(*this, &Send); - Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - - Xbyak::Label dont_process{}; - // Get increment - test(METHOD_ADDRESS, 0x3f000); - // If zero, method address doesn't update - je(dont_process); - - mov(ecx, METHOD_ADDRESS); - and_(METHOD_ADDRESS, 0xfff); - shr(ecx, 12); - and_(ecx, 0x3f); - lea(eax, ptr[rcx + METHOD_ADDRESS.cvt64()]); - sal(ecx, 12); - or_(eax, ecx); - - mov(METHOD_ADDRESS, eax); - - L(dont_process); -} - -void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) { - ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); - const s32 jump_address = - static_cast(pc) + static_cast(opcode.GetBranchTarget() / sizeof(s32)); - - Xbyak::Label end; - auto value = Compile_GetRegister(opcode.src_a, eax); - cmp(value, 0); // test(value, value); - if (optimizer.has_delayed_pc) { - switch (opcode.branch_condition) { - case Macro::BranchCondition::Zero: - jne(end, T_NEAR); - break; - case Macro::BranchCondition::NotZero: - je(end, T_NEAR); - break; - } - - if (opcode.branch_annul) { - xor_(BRANCH_HOLDER, BRANCH_HOLDER); - jmp(labels[jump_address], T_NEAR); - } else { - Xbyak::Label handle_post_exit{}; - Xbyak::Label skip{}; - jmp(skip, T_NEAR); - - L(handle_post_exit); - xor_(BRANCH_HOLDER, BRANCH_HOLDER); - jmp(labels[jump_address], T_NEAR); - - L(skip); - mov(BRANCH_HOLDER, handle_post_exit); - jmp(delay_skip[pc], T_NEAR); - } - } else { - switch (opcode.branch_condition) { - case Macro::BranchCondition::Zero: - je(labels[jump_address], T_NEAR); - break; - case Macro::BranchCondition::NotZero: - jne(labels[jump_address], T_NEAR); - break; - } - } - - L(end); -} - -void MacroJITx64Impl::Optimizer_ScanFlags() { - optimizer.can_skip_carry = true; - optimizer.has_delayed_pc = false; - for (auto raw_op : code) { - Macro::Opcode op{}; - op.raw = raw_op; - - if (op.operation == Macro::Operation::ALU) { - // Scan for any ALU operations which actually use the carry flag, if they don't exist in - // our current code we can skip emitting the carry flag handling operations - if (op.alu_operation == Macro::ALUOperation::AddWithCarry || - op.alu_operation == Macro::ALUOperation::SubtractWithBorrow) { - optimizer.can_skip_carry = false; - } - } - - if (op.operation == Macro::Operation::Branch) { - if (!op.branch_annul) { - optimizer.has_delayed_pc = true; - } - } - } -} - -void MacroJITx64Impl::Compile() { - labels.fill(Xbyak::Label()); - - Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); - // JIT state - mov(STATE, Common::X64::ABI_PARAM1); - mov(PARAMETERS, Common::X64::ABI_PARAM2); - mov(MAX_PARAMETER, Common::X64::ABI_PARAM3); - xor_(RESULT, RESULT); - xor_(METHOD_ADDRESS, METHOD_ADDRESS); - xor_(BRANCH_HOLDER, BRANCH_HOLDER); - - mov(dword[STATE + offsetof(JITState, registers) + 4], Compile_FetchParameter()); - - // Track get register for zero registers and mark it as no-op - optimizer.zero_reg_skip = true; - - // AddImmediate tends to be used as a NOP instruction, if we detect this we can - // completely skip the entire code path and no emit anything - optimizer.skip_dummy_addimmediate = true; - - // SMO tends to emit a lot of unnecessary method moves, we can mitigate this by only emitting - // one if our register isn't "dirty" - optimizer.optimize_for_method_move = true; - - // Enable run-time assertions in JITted code - optimizer.enable_asserts = false; - - // Check to see if we can skip emitting certain instructions - Optimizer_ScanFlags(); - - const u32 op_count = static_cast(code.size()); - for (u32 i = 0; i < op_count; i++) { - if (i < op_count - 1) { - pc = i + 1; - next_opcode = GetOpCode(); - } else { - next_opcode = {}; - } - pc = i; - Compile_NextInstruction(); - } - - L(end_of_code); - - Common::X64::ABI_PopRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); - ret(); - ready(); - program = getCode(); -} - -bool MacroJITx64Impl::Compile_NextInstruction() { - const auto opcode = GetOpCode(); - if (labels[pc].getAddress()) { - return false; - } - - L(labels[pc]); - - switch (opcode.operation) { - case Macro::Operation::ALU: - Compile_ALU(opcode); - break; - case Macro::Operation::AddImmediate: - Compile_AddImmediate(opcode); - break; - case Macro::Operation::ExtractInsert: - Compile_ExtractInsert(opcode); - break; - case Macro::Operation::ExtractShiftLeftImmediate: - Compile_ExtractShiftLeftImmediate(opcode); - break; - case Macro::Operation::ExtractShiftLeftRegister: - Compile_ExtractShiftLeftRegister(opcode); - break; - case Macro::Operation::Read: - Compile_Read(opcode); - break; - case Macro::Operation::Branch: - Compile_Branch(opcode); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented opcode {}", opcode.operation.Value()); - break; - } - - if (optimizer.has_delayed_pc) { - if (opcode.is_exit) { - mov(rax, end_of_code); - test(BRANCH_HOLDER, BRANCH_HOLDER); - cmove(BRANCH_HOLDER, rax); - // Jump to next instruction to skip delay slot check - je(labels[pc + 1], T_NEAR); - } else { - // TODO(ogniK): Optimize delay slot branching - Xbyak::Label no_delay_slot{}; - test(BRANCH_HOLDER, BRANCH_HOLDER); - je(no_delay_slot, T_NEAR); - mov(rax, BRANCH_HOLDER); - xor_(BRANCH_HOLDER, BRANCH_HOLDER); - jmp(rax); - L(no_delay_slot); - } - L(delay_skip[pc]); - if (opcode.is_exit) { - return false; - } - } else { - test(BRANCH_HOLDER, BRANCH_HOLDER); - jne(end_of_code, T_NEAR); - if (opcode.is_exit) { - inc(BRANCH_HOLDER); - return false; - } - } - return true; -} - -static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { - LOG_CRITICAL(HW_GPU, - "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", - parameter, max_parameter - sizeof(u32)); -} - -Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { - Xbyak::Label parameter_ok{}; - cmp(PARAMETERS, MAX_PARAMETER); - jb(parameter_ok, T_NEAR); - Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - mov(Common::X64::ABI_PARAM1, PARAMETERS); - mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); - Common::X64::CallFarFunction(*this, &WarnInvalidParameter); - Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - L(parameter_ok); - mov(eax, dword[PARAMETERS]); - add(PARAMETERS, sizeof(u32)); - return eax; -} - -Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) { - if (index == 0) { - // Register 0 is always zero - xor_(dst, dst); - } else { - mov(dst, dword[STATE + offsetof(JITState, registers) + index * sizeof(u32)]); - } - - return dst; -} - -void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) { - const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) { - // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero - // register. - if (reg_index == 0) { - return; - } - mov(dword[STATE + offsetof(JITState, registers) + reg_index * sizeof(u32)], result); - }; - const auto SetMethodAddress = [this](const Xbyak::Reg32& reg32) { mov(METHOD_ADDRESS, reg32); }; - - switch (operation) { - case Macro::ResultOperation::IgnoreAndFetch: - SetRegister(reg, Compile_FetchParameter()); - break; - case Macro::ResultOperation::Move: - SetRegister(reg, RESULT); - break; - case Macro::ResultOperation::MoveAndSetMethod: - SetRegister(reg, RESULT); - SetMethodAddress(RESULT); - break; - case Macro::ResultOperation::FetchAndSend: - // Fetch parameter and send result. - SetRegister(reg, Compile_FetchParameter()); - Compile_Send(RESULT); - break; - case Macro::ResultOperation::MoveAndSend: - // Move and send result. - SetRegister(reg, RESULT); - Compile_Send(RESULT); - break; - case Macro::ResultOperation::FetchAndSetMethod: - // Fetch parameter and use result as Method Address. - SetRegister(reg, Compile_FetchParameter()); - SetMethodAddress(RESULT); - break; - case Macro::ResultOperation::MoveAndSetMethodFetchAndSend: - // Move result and use as Method Address, then fetch and send parameter. - SetRegister(reg, RESULT); - SetMethodAddress(RESULT); - Compile_Send(Compile_FetchParameter()); - break; - case Macro::ResultOperation::MoveAndSetMethodSend: - // Move result and use as Method Address, then send bits 12:17 of result. - SetRegister(reg, RESULT); - SetMethodAddress(RESULT); - shr(RESULT, 12); - and_(RESULT, 0b111111); - Compile_Send(RESULT); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation); - break; - } -} - -Macro::Opcode MacroJITx64Impl::GetOpCode() const { - ASSERT(pc < code.size()); - return {code[pc]}; -} -} // Anonymous namespace - -MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d_) - : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {} - -std::unique_ptr MacroJITx64::Compile(const std::vector& code) { - return std::make_unique(maxwell3d, code); -} -} // namespace Tegra diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h deleted file mode 100644 index 99ee1b9e68..0000000000 --- a/src/video_core/macro/macro_jit_x64.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/common_types.h" -#include "video_core/macro/macro.h" - -namespace Tegra { - -namespace Engines { -class Maxwell3D; -} - -class MacroJITx64 final : public MacroEngine { -public: - explicit MacroJITx64(Engines::Maxwell3D& maxwell3d_); - -protected: - std::unique_ptr Compile(const std::vector& code) override; - -private: - Engines::Maxwell3D& maxwell3d; -}; - -} // namespace Tegra diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 9a69ea4e06..661fbef2b0 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp @@ -8,7 +8,7 @@ #include #include #include "common/cityhash.h" -#include "common/settings.h" // for enum class Settings::ShaderBackend +#include "common/settings.h" #include "video_core/renderer_opengl/gl_compute_pipeline.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -37,19 +37,20 @@ ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cac std::vector code_v, bool force_context_flush) : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_}, info{info_} { - switch (device.GetShaderBackend()) { - case Settings::ShaderBackend::Glsl: + switch (::Settings::values.renderer_backend.GetValue()) { + case Settings::RendererBackend::OpenGL_GLSL: source_program = CreateProgram(code, GL_COMPUTE_SHADER); break; - case Settings::ShaderBackend::Glasm: + case Settings::RendererBackend::OpenGL_GLASM: assembly_program = CompileProgram(code, GL_COMPUTE_PROGRAM_NV); break; - case Settings::ShaderBackend::SpirV: + case Settings::RendererBackend::OpenGL_SPIRV: source_program = CreateProgram(code_v, GL_COMPUTE_SHADER); break; + default: + UNREACHABLE(); } - std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), - uniform_buffer_sizes.begin()); + std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), uniform_buffer_sizes.begin()); num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index f5bf995d00..b49568b77d 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -233,19 +233,17 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) { // uniform buffers as "push constants" has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data; - shader_backend = Settings::values.shader_backend.GetValue(); - use_assembly_shaders = shader_backend == Settings::ShaderBackend::Glasm && - GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 && - GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2; - if (shader_backend == Settings::ShaderBackend::Glasm && !use_assembly_shaders) { - LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported"); - shader_backend = Settings::ShaderBackend::Glsl; + auto const shader_backend = Settings::values.renderer_backend.GetValue(); + use_assembly_shaders = shader_backend == Settings::RendererBackend::OpenGL_GLASM + && GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 + && GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2; + if (shader_backend == Settings::RendererBackend::OpenGL_GLASM && !use_assembly_shaders) { + LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported - expect instability!"); } - if (shader_backend == Settings::ShaderBackend::Glsl && is_nvidia) { + if (shader_backend == Settings::RendererBackend::OpenGL_GLSL && is_nvidia) { const std::string driver_version = version.substr(13); - const int version_major = - std::atoi(driver_version.substr(0, driver_version.find(".")).data()); + const int version_major = std::atoi(driver_version.substr(0, driver_version.find(".")).data()); if (version_major >= 495) { has_cbuf_ftou_bug = true; has_bool_ref_bug = true; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index a5a6bbbba7..17b5a828f2 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,10 +13,6 @@ #include "core/frontend/emu_window.h" #include "shader_recompiler/stage.h" -namespace Settings { -enum class ShaderBackend : u32; -}; - namespace OpenGL { class Device { @@ -168,10 +167,6 @@ public: return has_bool_ref_bug; } - Settings::ShaderBackend GetShaderBackend() const { - return shader_backend; - } - bool IsAmd() const { return vendor_name == "ATI Technologies Inc."; } @@ -208,8 +203,6 @@ private: u32 max_compute_shared_memory_size{}; u32 max_glasm_storage_buffer_blocks{}; - Settings::ShaderBackend shader_backend{}; - bool has_warp_intrinsics{}; bool has_shader_ballot{}; bool has_vertex_viewport_layer{}; diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index a5815b76d4..2abbd0de78 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -10,7 +10,7 @@ #include #include #include -#include "common/settings.h" // for enum class Settings::ShaderBackend +#include "common/settings.h" #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" #include "video_core/renderer_opengl/gl_graphics_pipeline.h" @@ -224,8 +224,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c ASSERT(num_textures <= MAX_TEXTURES); ASSERT(num_images <= MAX_IMAGES); - const auto backend = device.GetShaderBackend(); - const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm}; + const auto backend = ::Settings::values.renderer_backend.GetValue(); + const bool assembly_shaders = backend == Settings::RendererBackend::OpenGL_GLASM; use_storage_buffers = !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); writes_global_memory &= !use_storage_buffers; @@ -240,21 +240,19 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c force_context_flush](ShaderContext::Context*) mutable { for (size_t stage = 0; stage < 5; ++stage) { switch (backend) { - case Settings::ShaderBackend::Glsl: - if (!sources_[stage].empty()) { + case Settings::RendererBackend::OpenGL_GLSL: + if (!sources_[stage].empty()) source_programs[stage] = CreateProgram(sources_[stage], Stage(stage)); - } break; - case Settings::ShaderBackend::Glasm: - if (!sources_[stage].empty()) { - assembly_programs[stage] = - CompileProgram(sources_[stage], AssemblyStage(stage)); - } + case Settings::RendererBackend::OpenGL_GLASM: + if (!sources_[stage].empty()) + assembly_programs[stage] = CompileProgram(sources_[stage], AssemblyStage(stage)); break; - case Settings::ShaderBackend::SpirV: - if (!sources_spirv_[stage].empty()) { + case Settings::RendererBackend::OpenGL_SPIRV: + if (!sources_spirv_[stage].empty()) source_programs[stage] = CreateProgram(sources_spirv_[stage], Stage(stage)); - } + break; + default: break; } } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 45f729698e..83ef393481 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -465,8 +465,8 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( Shader::IR::Program* layer_source_program{}; for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { - const bool is_emulated_stage = layer_source_program != nullptr && - index == static_cast(Maxwell::ShaderType::Geometry); + const bool is_emulated_stage = layer_source_program != nullptr + && index == u32(Maxwell::ShaderType::Geometry); if (key.unique_hashes[index] == 0 && is_emulated_stage) { auto topology = MaxwellToOutputTopology(key.gs_input_topology); programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info, @@ -479,7 +479,7 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( Shader::Environment& env{*envs[env_index]}; ++env_index; - const u32 cfg_offset{static_cast(env.StartAddress() + sizeof(Shader::ProgramHeader))}; + const u32 cfg_offset = u32(env.StartAddress() + sizeof(Shader::ProgramHeader)); Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); if (Settings::values.dump_shaders) { @@ -490,14 +490,12 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( // Normal path programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); - total_storage_buffers += - Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors); + total_storage_buffers += Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors); } else { // VertexB path when VertexA is present. auto& program_va{programs[0]}; auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; - total_storage_buffers += - Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors); + total_storage_buffers += Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors); programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); } @@ -505,8 +503,8 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( layer_source_program = &programs[index]; } } - const u32 glasm_storage_buffer_limit{device.GetMaxGLASMStorageBufferBlocks()}; - const bool glasm_use_storage_buffers{total_storage_buffers <= glasm_storage_buffer_limit}; + const u32 glasm_storage_buffer_limit = device.GetMaxGLASMStorageBufferBlocks(); + const bool glasm_use_storage_buffers = total_storage_buffers <= glasm_storage_buffer_limit; std::array infos{}; @@ -514,7 +512,7 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( std::array, 5> sources_spirv; Shader::Backend::Bindings binding; Shader::IR::Program* previous_program{}; - const bool use_glasm{device.UseAssemblyShaders()}; + const bool use_glasm = device.UseAssemblyShaders(); const size_t first_index = uses_vertex_a && uses_vertex_b ? 1 : 0; for (size_t index = first_index; index < Maxwell::MaxShaderProgram; ++index) { const bool is_emulated_stage = layer_source_program != nullptr && @@ -528,21 +526,21 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( const size_t stage_index{index - 1}; infos[stage_index] = &program.info; - const auto runtime_info{ - MakeRuntimeInfo(key, program, previous_program, glasm_use_storage_buffers, use_glasm)}; - switch (device.GetShaderBackend()) { - case Settings::ShaderBackend::Glsl: + const auto runtime_info = MakeRuntimeInfo(key, program, previous_program, glasm_use_storage_buffers, use_glasm); + switch (::Settings::values.renderer_backend.GetValue()) { + case Settings::RendererBackend::OpenGL_GLSL: ConvertLegacyToGeneric(program, runtime_info); sources[stage_index] = EmitGLSL(profile, runtime_info, program, binding); break; - case Settings::ShaderBackend::Glasm: + case Settings::RendererBackend::OpenGL_GLASM: sources[stage_index] = EmitGLASM(profile, runtime_info, program, binding); break; - case Settings::ShaderBackend::SpirV: + case Settings::RendererBackend::OpenGL_SPIRV: ConvertLegacyToGeneric(program, runtime_info); - sources_spirv[stage_index] = - EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output); + sources_spirv[stage_index] = EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output); break; + default: + UNREACHABLE(); } previous_program = &program; } @@ -592,20 +590,20 @@ std::unique_ptr ShaderCache::CreateComputePipeline( std::string code{}; std::vector code_spirv; - switch (device.GetShaderBackend()) { - case Settings::ShaderBackend::Glsl: + switch (::Settings::values.renderer_backend.GetValue()) { + case Settings::RendererBackend::OpenGL_GLSL: code = EmitGLSL(profile, program); break; - case Settings::ShaderBackend::Glasm: + case Settings::RendererBackend::OpenGL_GLASM: code = EmitGLASM(profile, info, program); break; - case Settings::ShaderBackend::SpirV: + case Settings::RendererBackend::OpenGL_SPIRV: code_spirv = EmitSPIRV(profile, program, this->optimize_spirv_output); break; + default: + UNREACHABLE(); } - - return std::make_unique(device, texture_cache, buffer_cache, program_manager, - program.info, code, code_spirv, force_context_flush); + return std::make_unique(device, texture_cache, buffer_cache, program_manager, program.info, code, code_spirv, force_context_flush); } catch (Shader::Exception& exception) { LOG_ERROR(Render_OpenGL, "{}", exception.what()); return nullptr; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index a3957e4d9f..958988f27e 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -556,7 +556,7 @@ void TextureCacheRuntime::Finish() { glFinish(); } -StagingBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) { +StagingBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size, bool deferred) { return staging_buffer_pool.RequestUploadBuffer(size); } @@ -651,7 +651,8 @@ void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src, } void TextureCacheRuntime::AccelerateImageUpload(Image& image, const StagingBufferMap& map, - std::span swizzles) { + std::span swizzles, + u32 z_start, u32 z_count) { switch (image.info.type) { case ImageType::e2D: if (IsPixelFormatASTC(image.info.format)) { @@ -1213,19 +1214,16 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageV ImageView::~ImageView() = default; GLuint ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) { - if (image_format == Shader::ImageFormat::Typeless) { + if (image_format == Shader::ImageFormat::Typeless) return Handle(texture_type); - } - const bool is_signed{image_format == Shader::ImageFormat::R8_SINT || - image_format == Shader::ImageFormat::R16_SINT}; - if (!storage_views) { - storage_views = std::make_unique(); - } + const bool is_signed = image_format == Shader::ImageFormat::R8_SINT + || image_format == Shader::ImageFormat::R16_SINT; + if (!storage_views) + storage_views = {OpenGL::ImageView::StorageViews{}}; auto& type_views{is_signed ? storage_views->signeds : storage_views->unsigneds}; - GLuint& view{type_views[static_cast(texture_type)]}; - if (view == 0) { + GLuint& view{type_views[size_t(texture_type)]}; + if (view == 0) view = MakeView(texture_type, ShaderFormat(image_format)); - } return view; } diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index d4165d8e4d..e2a2022cb2 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -72,7 +75,7 @@ public: void Finish(); - StagingBufferMap UploadStagingBuffer(size_t size); + StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false); StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false); @@ -116,7 +119,8 @@ public: Tegra::Engines::Fermi2D::Operation operation); void AccelerateImageUpload(Image& image, const StagingBufferMap& map, - std::span swizzles); + std::span swizzles, + u32 z_start, u32 z_count); void InsertUploadMemoryBarrier(); @@ -223,6 +227,8 @@ public: bool ScaleDown(bool ignore = false); + u64 allocation_tick; + private: void CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); @@ -296,7 +302,7 @@ private: std::array views{}; std::vector stored_views; - std::unique_ptr storage_views; + std::optional storage_views; GLenum internal_format = GL_NONE; GLuint default_handle = 0; u32 buffer_size = 0; diff --git a/src/video_core/renderer_opengl/present/layer.cpp b/src/video_core/renderer_opengl/present/layer.cpp index 6c7092d229..d0a8e1186e 100644 --- a/src/video_core/renderer_opengl/present/layer.cpp +++ b/src/video_core/renderer_opengl/present/layer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -54,12 +57,12 @@ GLuint Layer::ConfigureDraw(std::array& out_matrix, switch (anti_aliasing) { case Settings::AntiAliasing::Fxaa: CreateFXAA(); - texture = fxaa->Draw(program_manager, info.display_texture); + texture = std::get(anti_alias).Draw(program_manager, info.display_texture); break; case Settings::AntiAliasing::Smaa: default: CreateSMAA(); - texture = smaa->Draw(program_manager, info.display_texture); + texture = std::get(anti_alias).Draw(program_manager, info.display_texture); break; } } @@ -68,7 +71,7 @@ GLuint Layer::ConfigureDraw(std::array& out_matrix, if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) { if (!fsr || fsr->NeedsRecreation(layout.screen)) { - fsr = std::make_unique(layout.screen.GetWidth(), layout.screen.GetHeight()); + fsr.emplace(layout.screen.GetWidth(), layout.screen.GetHeight()); } texture = fsr->Draw(program_manager, texture, info.scaled_width, info.scaled_height, crop); @@ -199,23 +202,20 @@ void Layer::ConfigureFramebufferTexture(const Tegra::FramebufferConfig& framebuf glTextureStorage2D(framebuffer_texture.resource.handle, 1, internal_format, framebuffer_texture.width, framebuffer_texture.height); - fxaa.reset(); - smaa.reset(); + anti_alias.emplace(); } void Layer::CreateFXAA() { - smaa.reset(); - if (!fxaa) { - fxaa = std::make_unique( + if (!std::holds_alternative(anti_alias)) { + anti_alias.emplace( Settings::values.resolution_info.ScaleUp(framebuffer_texture.width), Settings::values.resolution_info.ScaleUp(framebuffer_texture.height)); } } void Layer::CreateSMAA() { - fxaa.reset(); - if (!smaa) { - smaa = std::make_unique( + if (!std::holds_alternative(anti_alias)) { + anti_alias.emplace( Settings::values.resolution_info.ScaleUp(framebuffer_texture.width), Settings::values.resolution_info.ScaleUp(framebuffer_texture.height)); } diff --git a/src/video_core/renderer_opengl/present/layer.h b/src/video_core/renderer_opengl/present/layer.h index 5b15b730fc..7a58aee7b9 100644 --- a/src/video_core/renderer_opengl/present/layer.h +++ b/src/video_core/renderer_opengl/present/layer.h @@ -1,13 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include +#include #include #include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/present/smaa.h" +#include "video_core/renderer_opengl/present/fxaa.h" +#include "video_core/renderer_opengl/present/fsr.h" namespace Layout { struct FramebufferLayout; @@ -26,11 +33,8 @@ struct FramebufferConfig; namespace OpenGL { struct FramebufferTextureInfo; -class FSR; -class FXAA; class ProgramManager; class RasterizerOpenGL; -class SMAA; /// Structure used for storing information about the textures for the Switch screen struct TextureInfo { @@ -76,9 +80,8 @@ private: /// Display information for Switch screen TextureInfo framebuffer_texture; - std::unique_ptr fsr; - std::unique_ptr fxaa; - std::unique_ptr smaa; + std::optional fsr; + std::variant anti_alias; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp index 37fb766139..5f3461a913 100644 --- a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -114,11 +114,7 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li } glBindTextureUnit(0, textures[i]); - glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE, - matrices[i].data()); - glProgramUniform2ui(frag.handle, ScreenSizeLocation, - static_cast(layout.screen.GetWidth()), - static_cast(layout.screen.GetHeight())); + glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE, matrices[i].data()); glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i])); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } diff --git a/src/video_core/renderer_vulkan/present/anti_alias_pass.h b/src/video_core/renderer_vulkan/present/anti_alias_pass.h index 1f20fbd7f0..4990e87502 100644 --- a/src/video_core/renderer_vulkan/present/anti_alias_pass.h +++ b/src/video_core/renderer_vulkan/present/anti_alias_pass.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,10 +19,4 @@ public: VkImageView* inout_image_view) = 0; }; -class NoAA final : public AntiAliasPass { -public: - void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, - VkImageView* inout_image_view) override {} -}; - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/filters.cpp b/src/video_core/renderer_vulkan/present/filters.cpp index 0a28ea6349..6203b41218 100644 --- a/src/video_core/renderer_vulkan/present/filters.cpp +++ b/src/video_core/renderer_vulkan/present/filters.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -30,32 +30,19 @@ namespace Vulkan { -namespace { - -vk::ShaderModule SelectScaleForceShader(const Device& device) { - if (device.IsFloat16Supported()) { - return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP16_FRAG_SPV); - } else { - return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP32_FRAG_SPV); - } -} - -} // Anonymous namespace - std::unique_ptr MakeNearestNeighbor(const Device& device, VkFormat frame_format) { - return std::make_unique(device, frame_format, - CreateNearestNeighborSampler(device), - BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); + return std::make_unique(device, frame_format, CreateNearestNeighborSampler(device), + BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); } std::unique_ptr MakeBilinear(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), - BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); + BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); } std::unique_ptr MakeSpline1(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), - BuildShader(device, PRESENT_SPLINE1_FRAG_SPV)); + BuildShader(device, PRESENT_SPLINE1_FRAG_SPV)); } std::unique_ptr MakeBicubic(const Device& device, VkFormat frame_format, VkCubicFilterWeightsQCOM qcom_weights) { @@ -84,22 +71,26 @@ std::unique_ptr MakeBicubic(const Device& device, VkFormat fram std::unique_ptr MakeGaussian(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), - BuildShader(device, PRESENT_GAUSSIAN_FRAG_SPV)); + BuildShader(device, PRESENT_GAUSSIAN_FRAG_SPV)); } std::unique_ptr MakeLanczos(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), - BuildShader(device, PRESENT_LANCZOS_FRAG_SPV)); + BuildShader(device, PRESENT_LANCZOS_FRAG_SPV)); } std::unique_ptr MakeScaleForce(const Device& device, VkFormat frame_format) { - return std::make_unique(device, frame_format, CreateBilinearSampler(device), - SelectScaleForceShader(device)); + auto const select_fn = [&]() { + return device.IsFloat16Supported() + ? BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP16_FRAG_SPV) + : BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP32_FRAG_SPV); + }; + return std::make_unique(device, frame_format, CreateBilinearSampler(device), select_fn()); } std::unique_ptr MakeArea(const Device& device, VkFormat frame_format) { return std::make_unique(device, frame_format, CreateBilinearSampler(device), - BuildShader(device, PRESENT_AREA_FRAG_SPV)); + BuildShader(device, PRESENT_AREA_FRAG_SPV)); } std::unique_ptr MakeMmpx(const Device& device, VkFormat frame_format) { diff --git a/src/video_core/renderer_vulkan/present/fsr.cpp b/src/video_core/renderer_vulkan/present/fsr.cpp index 3f708be704..ba6252ed95 100644 --- a/src/video_core/renderer_vulkan/present/fsr.cpp +++ b/src/video_core/renderer_vulkan/present/fsr.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -41,25 +44,18 @@ FSR::FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_c void FSR::CreateImages() { m_dynamic_images.resize(m_image_count); for (auto& images : m_dynamic_images) { - images.images[Easu] = - CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); - images.images[Rcas] = - CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); - images.image_views[Easu] = - CreateWrappedImageView(m_device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT); - images.image_views[Rcas] = - CreateWrappedImageView(m_device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT); + images.images[Easu] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.images[Rcas] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Easu] = CreateWrappedImageView(m_device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Rcas] = CreateWrappedImageView(m_device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT); } } void FSR::CreateRenderPasses() { m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); - for (auto& images : m_dynamic_images) { - images.framebuffers[Easu] = - CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Easu], m_extent); - images.framebuffers[Rcas] = - CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Rcas], m_extent); + images.framebuffers[Easu] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Easu], m_extent); + images.framebuffers[Rcas] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Rcas], m_extent); } } @@ -87,16 +83,13 @@ void FSR::CreateDescriptorPool() { } void FSR::CreateDescriptorSetLayout() { - m_descriptor_set_layout = - CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + m_descriptor_set_layout = CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); } void FSR::CreateDescriptorSets() { std::vector layouts(MaxFsrStage, *m_descriptor_set_layout); - - for (auto& images : m_dynamic_images) { + for (auto& images : m_dynamic_images) images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts); - } } void FSR::CreatePipelineLayouts() { @@ -128,31 +121,25 @@ void FSR::CreatePipelines() { void FSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { Images& images = m_dynamic_images[image_index]; std::vector image_infos; - std::vector updates; image_infos.reserve(2); - - updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, - images.descriptor_sets[Easu], 0)); - updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Easu], - images.descriptor_sets[Rcas], 0)); - + std::vector updates{ + CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, images.descriptor_sets[Easu], 0), + CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Easu], images.descriptor_sets[Rcas], 0) + }; m_device.GetLogical().UpdateDescriptorSets(updates, {}); } void FSR::UploadImages(Scheduler& scheduler) { - if (m_images_ready) { - return; + if (!m_images_ready) { + m_images_ready = true; + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& image : m_dynamic_images) { + ClearColorImage(cmdbuf, *image.images[Easu]); + ClearColorImage(cmdbuf, *image.images[Rcas]); + } + }); + scheduler.Finish(); } - - scheduler.Record([&](vk::CommandBuffer cmdbuf) { - for (auto& image : m_dynamic_images) { - ClearColorImage(cmdbuf, *image.images[Easu]); - ClearColorImage(cmdbuf, *image.images[Rcas]); - } - }); - scheduler.Finish(); - - m_images_ready = true; } VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp index 5676dfe62a..b462c672cc 100644 --- a/src/video_core/renderer_vulkan/present/layer.cpp +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -1,10 +1,15 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "video_core/present.h" +#include "video_core/renderer_vulkan/present/anti_alias_pass.h" +/* X11 defines */ +#undef Success +#undef BadValue #include "video_core/renderer_vulkan/vk_rasterizer.h" #include "common/settings.h" @@ -58,7 +63,7 @@ Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Schedule CreateDescriptorPool(); CreateDescriptorSets(layout); if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) { - CreateFSR(output_size); + fsr.emplace(device, memory_allocator, image_count, output_size); } } @@ -97,7 +102,11 @@ void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, VkImageView source_image_view = texture_info ? texture_info->image_view : *raw_image_views[image_index]; - anti_alias->Draw(scheduler, image_index, &source_image, &source_image_view); + if (auto* fxaa = std::get_if(&anti_alias)) { + fxaa->Draw(scheduler, image_index, &source_image, &source_image_view); + } else if (auto* smaa = std::get_if(&anti_alias)) { + smaa->Draw(scheduler, image_index, &source_image, &source_image_view); + } auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height); const VkExtent2D render_extent{ @@ -106,8 +115,7 @@ void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, }; if (fsr) { - source_image_view = fsr->Draw(scheduler, image_index, source_image, source_image_view, - render_extent, crop_rect); + source_image_view = fsr->Draw(scheduler, image_index, source_image, source_image_view, render_extent, crop_rect); crop_rect = {0, 0, 1, 1}; } @@ -156,10 +164,6 @@ void Layer::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { } } -void Layer::CreateFSR(VkExtent2D output_size) { - fsr = std::make_unique(device, memory_allocator, image_count, output_size); -} - void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { if (framebuffer.width == raw_width && framebuffer.height == raw_height && framebuffer.pixel_format == pixel_format && !raw_images.empty()) { @@ -169,7 +173,7 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { raw_width = framebuffer.width; raw_height = framebuffer.height; pixel_format = framebuffer.pixel_format; - anti_alias.reset(); + anti_alias.emplace(); ReleaseRawImages(); CreateStagingBuffer(framebuffer); @@ -177,9 +181,8 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { } void Layer::SetAntiAliasPass() { - if (anti_alias && anti_alias_setting == filters.get_anti_aliasing()) { + if (!std::holds_alternative(anti_alias) && anti_alias_setting == filters.get_anti_aliasing()) return; - } anti_alias_setting = filters.get_anti_aliasing(); @@ -190,13 +193,13 @@ void Layer::SetAntiAliasPass() { switch (anti_alias_setting) { case Settings::AntiAliasing::Fxaa: - anti_alias = std::make_unique(device, memory_allocator, image_count, render_area); + anti_alias.emplace(device, memory_allocator, image_count, render_area); break; case Settings::AntiAliasing::Smaa: - anti_alias = std::make_unique(device, memory_allocator, image_count, render_area); + anti_alias.emplace(device, memory_allocator, image_count, render_area); break; default: - anti_alias = std::make_unique(); + anti_alias.emplace(); break; } } diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h index f5effdcd7f..d38b81823e 100644 --- a/src/video_core/renderer_vulkan/present/layer.h +++ b/src/video_core/renderer_vulkan/present/layer.h @@ -1,11 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include +#include + #include "common/math_util.h" #include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/renderer_vulkan/present/fsr.h" +#include "video_core/renderer_vulkan/present/fxaa.h" +#include "video_core/renderer_vulkan/present/smaa.h" namespace Layout { struct FramebufferLayout; @@ -29,7 +38,6 @@ namespace Vulkan { class AntiAliasPass; class Device; -class FSR; class MemoryAllocator; struct PresentPushConstants; class RasterizerVulkan; @@ -54,7 +62,6 @@ private: void CreateDescriptorSets(VkDescriptorSetLayout layout); void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer); void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); - void CreateFSR(VkExtent2D output_size); void RefreshResources(const Tegra::FramebufferConfig& framebuffer); void SetAntiAliasPass(); @@ -87,9 +94,8 @@ private: Service::android::PixelFormat pixel_format{}; Settings::AntiAliasing anti_alias_setting{}; - std::unique_ptr anti_alias{}; - - std::unique_ptr fsr{}; + std::variant anti_alias{}; + std::optional fsr{}; std::vector resource_ticks{}; }; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 3b47570a7e..d1e607e75f 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -193,10 +193,13 @@ void RendererVulkan::Composite(std::span framebu RenderScreenshot(framebuffers); Frame* frame = present_manager.GetRenderFrame(); + + scheduler.RequestOutsideRenderPassOperationContext(); blit_swapchain.DrawToFrame(rasterizer, frame, framebuffers, render_window.GetFramebufferLayout(), swapchain.GetImageCount(), swapchain.GetImageViewFormat()); scheduler.Flush(*frame->render_ready); + present_manager.Present(frame); gpu.RendererFrameEndNotify(); @@ -303,6 +306,7 @@ void RendererVulkan::RenderAppletCaptureLayer( VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat); } + scheduler.RequestOutsideRenderPassOperationContext(); blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1, CaptureFormat); } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 8077860ae9..6256bc8bd8 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -335,13 +335,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m compute_pass_descriptor_queue) { const VkDriverIdKHR driver_id = device.GetDriverID(); limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || - driver_id == VK_DRIVER_ID_MESA_TURNIP || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; if (limit_dynamic_storage_buffers) { max_dynamic_storage_buffers = device.GetMaxDescriptorSetStorageBuffersDynamic(); - } - if (device.GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY) { - // TODO: FixMe: Uint8Pass compute shader does not build on some Qualcomm drivers. + } + if (device.SupportsUint8Indices()) { uint8_pass = std::make_unique(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue); } @@ -519,6 +517,10 @@ void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat vk_index_type = VK_INDEX_TYPE_UINT16; if (uint8_pass) { std::tie(vk_buffer, vk_offset) = uint8_pass->Assemble(num_indices, buffer, offset); + } else if (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) { + ReserveNullBuffer(); + vk_buffer = *null_buffer; + vk_offset = 0; } } if (vk_buffer == VK_NULL_HANDLE) { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index d5d806fc69..b73fcd162b 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -184,7 +184,7 @@ private: QuadIndexedPass quad_index_pass; bool limit_dynamic_storage_buffers = false; - u32 max_dynamic_storage_buffers = std::numeric_limits::max(); + u32 max_dynamic_storage_buffers = (std::numeric_limits::max)(); }; struct BufferCacheParams { diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 5b41dc225f..0ae81af0fb 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -24,6 +24,7 @@ #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -622,7 +623,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -637,9 +638,292 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, }, }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, image_barrier); + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, image_barrier); + }); +} + +constexpr u32 BL3D_BINDING_SWIZZLE_TABLE = 0; +constexpr u32 BL3D_BINDING_INPUT_BUFFER = 1; +constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 2; + +constexpr std::array BL3D_DESCRIPTOR_SET_BINDINGS{{ + { + .binding = BL3D_BINDING_SWIZZLE_TABLE, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // swizzle_table[] + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + { + .binding = BL3D_BINDING_INPUT_BUFFER, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + { + .binding = BL3D_BINDING_OUTPUT_BUFFER, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, +}}; + +constexpr DescriptorBankInfo BL3D_BANK_INFO{ + .uniform_buffers = 0, + .storage_buffers = 3, + .texture_buffers = 0, + .image_buffers = 0, + .textures = 0, + .images = 0, + .score = 3, +}; + +constexpr std::array + BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ + { + .dstBinding = BL3D_BINDING_SWIZZLE_TABLE, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = BL3D_BINDING_SWIZZLE_TABLE * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + }, + { + .dstBinding = BL3D_BINDING_INPUT_BUFFER, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + }, + { + .dstBinding = BL3D_BINDING_OUTPUT_BUFFER, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + } + }}; + +struct alignas(16) BlockLinearUnswizzle3DPushConstants { + u32 blocks_dim[3]; // Offset 0 + u32 bytes_per_block_log2; // Offset 12 + + u32 origin[3]; // Offset 16 + u32 slice_size; // Offset 28 + + u32 block_size; // Offset 32 + u32 x_shift; // Offset 36 + u32 block_height; // Offset 40 + u32 block_height_mask; // Offset 44 + + u32 block_depth; // Offset 48 + u32 block_depth_mask; // Offset 52 + s32 _pad; // Offset 56 + + s32 destination[3]; // Offset 60 + s32 _pad_end; // Offset 72 +}; +static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128); + +BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass( + const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass( + device_, descriptor_pool_, + BL3D_DESCRIPTOR_SET_BINDINGS, + BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, + BL3D_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + BLOCK_LINEAR_UNSWIZZLE_3D_BCN_COMP_SPV), + scheduler{scheduler_}, + staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzle3DPass::~BlockLinearUnswizzle3DPass() = default; + +// God have mercy on my soul +void BlockLinearUnswizzle3DPass::Unswizzle( + Image& image, + const StagingBufferRef& swizzled, + std::span swizzles, + u32 z_start, u32 z_count) +{ + using namespace VideoCommon::Accelerated; + + const u32 MAX_BATCH_SLICES = (std::min)(z_count, image.info.size.depth); + + if (!image.has_compute_unswizzle_buffer) { + // Allocate exactly what this batch needs + image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); + } + + ASSERT(swizzles.size() == 1); + const auto& sw = swizzles[0]; + const auto params = MakeBlockLinearSwizzle3DParams(sw, image.info); + + const u32 blocks_x = (image.info.size.width + 3) / 4; + const u32 blocks_y = (image.info.size.height + 3) / 4; + + scheduler.RequestOutsideRenderPassOperationContext(); + for (u32 z_offset = 0; z_offset < z_count; z_offset += MAX_BATCH_SLICES) { + const u32 current_chunk_slices = (std::min)(MAX_BATCH_SLICES, z_count - z_offset); + const u32 current_z_start = z_start + z_offset; + + UnswizzleChunk(image, swizzled, sw, params, blocks_x, blocks_y, + current_z_start, current_chunk_slices); + } +} + +void BlockLinearUnswizzle3DPass::UnswizzleChunk( + Image& image, + const StagingBufferRef& swizzled, + const VideoCommon::SwizzleParameters& sw, + const BlockLinearSwizzle3DParams& params, + u32 blocks_x, u32 blocks_y, + u32 z_start, u32 z_count) +{ + BlockLinearUnswizzle3DPushConstants pc{}; + pc.origin[0] = params.origin[0]; + pc.origin[1] = params.origin[1]; + pc.origin[2] = z_start; // Current chunk's Z start + + pc.destination[0] = params.destination[0]; + pc.destination[1] = params.destination[1]; + pc.destination[2] = 0; // Shader writes to start of output buffer + + pc.bytes_per_block_log2 = params.bytes_per_block_log2; + pc.slice_size = params.slice_size; + pc.block_size = params.block_size; + pc.x_shift = params.x_shift; + pc.block_height = params.block_height; + pc.block_height_mask = params.block_height_mask; + pc.block_depth = params.block_depth; + pc.block_depth_mask = params.block_depth_mask; + + pc.blocks_dim[0] = blocks_x; + pc.blocks_dim[1] = blocks_y; + pc.blocks_dim[2] = z_count; // Only process the count + + compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.AddBuffer(*image.runtime->swizzle_table_buffer, 0, + image.runtime->swizzle_table_size); + compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, + sw.buffer_offset + swizzled.offset, + image.guest_size_bytes - sw.buffer_offset); + compute_pass_descriptor_queue.AddBuffer(*image.compute_unswizzle_buffer, 0, + image.compute_unswizzle_buffer_size); + + const void* descriptor_data = compute_pass_descriptor_queue.UpdateData(); + const VkDescriptorSet set = descriptor_allocator.Commit(); + + const u32 gx = Common::DivCeil(blocks_x, 8u); + const u32 gy = Common::DivCeil(blocks_y, 8u); + const u32 gz = Common::DivCeil(z_count, 4u); + + const u32 bytes_per_block = 1u << pc.bytes_per_block_log2; + const VkDeviceSize output_slice_size = + static_cast(blocks_x) * blocks_y * bytes_per_block; + const VkDeviceSize barrier_size = output_slice_size * z_count; + + const bool is_first_chunk = (z_start == 0); + + const VkBuffer out_buffer = *image.compute_unswizzle_buffer; + const VkImage dst_image = image.Handle(); + const VkImageAspectFlags aspect = image.AspectMask(); + const u32 image_width = image.info.size.width; + const u32 image_height = image.info.size.height; + + scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz, z_start, z_count, + barrier_size, is_first_chunk, out_buffer, dst_image, aspect, + image_width, image_height + ](vk::CommandBuffer cmdbuf) { + + if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) { + return; + } + + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc); + cmdbuf.Dispatch(gx, gy, gz); + + // Single barrier for compute -> transfer (buffer ready, image transition) + const VkBufferMemoryBarrier buffer_barrier{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .buffer = out_buffer, + .offset = 0, + .size = barrier_size, + }; + + // Image layout transition + const VkImageMemoryBarrier pre_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = is_first_chunk ? VkAccessFlags{} : + static_cast(VK_ACCESS_TRANSFER_WRITE_BIT), + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = is_first_chunk ? VK_IMAGE_LAYOUT_UNDEFINED : + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange = {aspect, 0, 1, 0, 1}, + }; + + // Single barrier handles both buffer and image + cmdbuf.PipelineBarrier( + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + nullptr, buffer_barrier, pre_barrier + ); + + // Copy chunk to correct Z position in image + const VkBufferImageCopy copy{ + .bufferOffset = 0, // Read from start of staging buffer + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {aspect, 0, 0, 1}, + .imageOffset = {0, 0, static_cast(z_start)}, // Write to correct Z + .imageExtent = {image_width, image_height, z_count}, + }; + cmdbuf.CopyBufferToImage(out_buffer, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy); + + // Post-copy transition + const VkImageMemoryBarrier post_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange = {aspect, 0, 1, 0, 1}, + }; + + cmdbuf.PipelineBarrier( + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, + nullptr, nullptr, post_barrier + ); }); - scheduler.Finish(); } MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 7b8f938c1c..0e5badce01 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,6 +17,7 @@ #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/texture_cache/accelerated_swizzle.h" namespace VideoCommon { struct SwizzleParameters; @@ -21,6 +25,8 @@ struct SwizzleParameters; namespace Vulkan { +using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; + class Device; class StagingBufferPool; class Scheduler; @@ -131,6 +137,34 @@ private: MemoryAllocator& memory_allocator; }; +class BlockLinearUnswizzle3DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzle3DPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzle3DPass(); + + void Unswizzle(Image& image, + const StagingBufferRef& swizzled, + std::span swizzles, + u32 z_start, u32 z_count); + + void UnswizzleChunk( + Image& image, + const StagingBufferRef& swizzled, + const VideoCommon::SwizzleParameters& sw, + const BlockLinearSwizzle3DParams& params, + u32 blocks_x, u32 blocks_y, + u32 z_start, u32 z_count); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + + class MSAACopyPass final : public ComputePass { public: explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e08eb517ad..d30496d8b8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -403,7 +403,6 @@ void RasterizerVulkan::DrawTexture() { } void RasterizerVulkan::Clear(u32 layer_count) { - FlushWork(); gpu_memory->FlushCaching(); @@ -423,9 +422,7 @@ void RasterizerVulkan::Clear(u32 layer_count) { scheduler.RequestRenderpass(framebuffer); query_cache.NotifySegment(true); - query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, - maxwell3d->regs.zpass_pixel_count_enable); - + query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); u32 up_scale = 1; u32 down_shift = 0; if (texture_cache.IsRescaling()) { @@ -470,14 +467,14 @@ void RasterizerVulkan::Clear(u32 layer_count) { offset = 0; return; } - if (offset >= static_cast(limit)) { - offset = static_cast(limit); + if (offset >= s32(limit)) { + offset = s32(limit); extent = 0; return; } - const u64 end_coord = static_cast(offset) + extent; + const u64 end_coord = u64(offset) + extent; if (end_coord > limit) { - extent = limit - static_cast(offset); + extent = limit - u32(offset); } }; @@ -491,30 +488,22 @@ void RasterizerVulkan::Clear(u32 layer_count) { const u32 color_attachment = regs.clear_surface.RT; if (use_color && framebuffer->HasAspectColorBit(color_attachment)) { - const auto format = - VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format); + const auto format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format); bool is_integer = IsPixelFormatInteger(format); bool is_signed = IsPixelFormatSignedInteger(format); size_t int_size = PixelComponentSizeBitsInteger(format); VkClearValue clear_value{}; if (!is_integer) { - std::memcpy(clear_value.color.float32, regs.clear_color.data(), - regs.clear_color.size() * sizeof(f32)); + std::memcpy(clear_value.color.float32, regs.clear_color.data(), regs.clear_color.size() * sizeof(f32)); } else if (!is_signed) { - for (size_t i = 0; i < 4; i++) { - clear_value.color.uint32[i] = static_cast( - static_cast(static_cast(int_size) << 1U) * regs.clear_color[i]); - } + for (size_t i = 0; i < 4; i++) + clear_value.color.uint32[i] = u32(f32(u64(int_size) << 1U) * regs.clear_color[i]); } else { - for (size_t i = 0; i < 4; i++) { - clear_value.color.int32[i] = - static_cast(static_cast(static_cast(int_size - 1) << 1) * - (regs.clear_color[i] - 0.5f)); - } + for (size_t i = 0; i < 4; i++) + clear_value.color.int32[i] = s32(f32(s64(int_size - 1) << 1) * (regs.clear_color[i] - 0.5f)); } - if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B && - regs.clear_surface.A) { + if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B && regs.clear_surface.A) { scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { const VkClearAttachment attachment{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, @@ -524,14 +513,11 @@ void RasterizerVulkan::Clear(u32 layer_count) { cmdbuf.ClearAttachments(attachment, clear_rect); }); } else { - u8 color_mask = static_cast(regs.clear_surface.R | regs.clear_surface.G << 1 | - regs.clear_surface.B << 2 | regs.clear_surface.A << 3); + u8 color_mask = u8(regs.clear_surface.R | regs.clear_surface.G << 1 | regs.clear_surface.B << 2 | regs.clear_surface.A << 3); Region2D dst_region = { Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, - Offset2D{.x = clear_rect.rect.offset.x + - static_cast(clear_rect.rect.extent.width), - .y = clear_rect.rect.offset.y + - static_cast(clear_rect.rect.extent.height)}}; + Offset2D{.x = clear_rect.rect.offset.x + s32(clear_rect.rect.extent.width), + .y = clear_rect.rect.offset.y + s32(clear_rect.rect.extent.height)}}; blit_image.ClearColor(framebuffer, color_mask, regs.clear_color, dst_region); } } @@ -554,11 +540,10 @@ void RasterizerVulkan::Clear(u32 layer_count) { regs.stencil_front_mask != 0) { Region2D dst_region = { Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, - Offset2D{.x = clear_rect.rect.offset.x + static_cast(clear_rect.rect.extent.width), - .y = clear_rect.rect.offset.y + - static_cast(clear_rect.rect.extent.height)}}; + Offset2D{.x = clear_rect.rect.offset.x + s32(clear_rect.rect.extent.width), + .y = clear_rect.rect.offset.y + s32(clear_rect.rect.extent.height)}}; blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, - static_cast(regs.stencil_front_mask), regs.clear_stencil, + u8(regs.stencil_front_mask), regs.clear_stencil, regs.stencil_front_func_mask, dst_region); } else { scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index f1bd0afb2f..44e49b0348 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -47,6 +47,16 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) : device{device_}, state_tracker{state_tracker_}, master_semaphore{std::make_unique(device)}, command_pool{std::make_unique(*master_semaphore, device)} { + + /*// PRE-OPTIMIZATION: Warm up the pool to prevent mid-frame spikes + { + std::scoped_lock rl{reserve_mutex}; + chunk_reserve.reserve(2048); // Prevent vector resizing + for (int i = 0; i < 1024; ++i) { + chunk_reserve.push_back(std::make_unique()); + } + }*/ + AcquireNewChunk(); AllocateWorkerCommandBuffer(); worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index de7cb0eb53..b8383fc566 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -25,12 +25,14 @@ #include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/surface.h" #include "video_core/texture_cache/formatter.h" #include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/util.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" +#include "video_core/textures/decoders.h" namespace Vulkan { @@ -653,16 +655,11 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im } void TryTransformSwizzleIfNeeded(PixelFormat format, std::array& swizzle, - bool emulate_bgr565, bool emulate_a4b4g4r4) { + bool emulate_a4b4g4r4) { switch (format) { case PixelFormat::A1B5G5R5_UNORM: std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); break; - case PixelFormat::B5G6R5_UNORM: - if (emulate_bgr565) { - std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); - } - break; case PixelFormat::A5B5G5R1_UNORM: std::ranges::transform(swizzle, swizzle.begin(), SwapSpecial); break; @@ -859,8 +856,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched compute_pass_descriptor_queue, memory_allocator); } if (device.IsStorageImageMultisampleSupported()) { - msaa_copy_pass = std::make_unique( - device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); + msaa_copy_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); } if (!device.IsKhrImageFormatListSupported()) { return; @@ -879,14 +875,51 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched } } } + + bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, + staging_buffer_pool, compute_pass_descriptor_queue); + + // --- Create swizzle table buffer --- + { + auto table = Tegra::Texture::MakeSwizzleTable(); + + swizzle_table_size = static_cast(table.size() * sizeof(table[0])); + + auto staging = staging_buffer_pool.Request(swizzle_table_size, MemoryUsage::Upload); + std::memcpy(staging.mapped_span.data(), table.data(), static_cast(swizzle_table_size)); + + VkBufferCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = swizzle_table_size, + .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + }; + swizzle_table_buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::DeviceLocal); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([staging_buf = staging.buffer, + dst_buf = *swizzle_table_buffer, + size = swizzle_table_size, + src_off = staging.offset](vk::CommandBuffer cmdbuf) { + + const VkBufferCopy region{ + .srcOffset = src_off, + .dstOffset = 0, + .size = size, + }; + cmdbuf.CopyBuffer(staging_buf, dst_buf, region); + }); + } } void TextureCacheRuntime::Finish() { scheduler.Finish(); } -StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size) { - return staging_buffer_pool.Request(size, MemoryUsage::Upload); +StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size, bool deferred) { + return staging_buffer_pool.Request(size, MemoryUsage::Upload, deferred); } StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) { @@ -1281,7 +1314,7 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im case PixelFormat::R32G32_FLOAT: case PixelFormat::R32G32_SINT: case PixelFormat::R32_FLOAT: - if (src_view.format == PixelFormat::D32_FLOAT) { + if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) { const Region2D region{ .start = {0, 0}, .end = {static_cast(dst->RenderArea().width), @@ -1582,6 +1615,46 @@ Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBas Image::~Image() = default; +void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { + if (has_compute_unswizzle_buffer) + return; + + using VideoCore::Surface::BytesPerBlock; + + const u32 block_bytes = BytesPerBlock(info.format); // 8 for BC1, 16 for BC6H + const u32 block_width = 4; + const u32 block_height = 4; + + // BCn is 4x4x1 blocks + const u32 blocks_x = (info.size.width + block_width - 1) / block_width; + const u32 blocks_y = (info.size.height + block_height - 1) / block_height; + const u32 blocks_z = (std::min)(max_slices, info.size.depth); + + const u64 block_count = + static_cast(blocks_x) * + static_cast(blocks_y) * + static_cast(blocks_z); + + compute_unswizzle_buffer_size = block_count * block_bytes; + + VkBufferCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = compute_unswizzle_buffer_size, + .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + + compute_unswizzle_buffer = + runtime->memory_allocator.CreateBuffer(ci, MemoryUsage::DeviceLocal); + + has_compute_unswizzle_buffer = true; +} + void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, std::span copies) { // TODO: Move this to another API @@ -1597,10 +1670,10 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, // CHANGE: Gate the MSAA path more strictly and only use it for color, when the pass and device // support are available. Avoid running the MSAA path when prerequisites aren't met, // preventing validation and runtime issues. - const bool wants_msaa_upload = info.num_samples > 1 && - (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && - runtime->CanUploadMSAA() && runtime->msaa_copy_pass != nullptr && - runtime->device.IsStorageImageMultisampleSupported(); + const bool wants_msaa_upload = info.num_samples > 1 + && (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 + && runtime->CanUploadMSAA() && runtime->msaa_copy_pass.has_value() + && runtime->device.IsStorageImageMultisampleSupported(); if (wants_msaa_upload) { // Create a temporary non-MSAA image to upload the data first @@ -1969,8 +2042,7 @@ bool Image::BlitScaleHelper(bool scale_up) { const u32 scaled_width = resolution.ScaleUp(info.size.width); const u32 scaled_height = is_2d ? resolution.ScaleUp(info.size.height) : info.size.height; std::unique_ptr& blit_view = scale_up ? scale_view : normal_view; - std::unique_ptr& blit_framebuffer = - scale_up ? scale_framebuffer : normal_framebuffer; + std::optional& blit_framebuffer = scale_up ? scale_framebuffer : normal_framebuffer; if (!blit_view) { const auto view_info = ImageViewInfo(ImageViewType::e2D, info.format); blit_view = std::make_unique(*runtime, view_info, NULL_IMAGE_ID, *this); @@ -1982,11 +2054,11 @@ bool Image::BlitScaleHelper(bool scale_up) { const u32 dst_height = scale_up ? scaled_height : info.size.height; const Region2D src_region{ .start = {0, 0}, - .end = {static_cast(src_width), static_cast(src_height)}, + .end = {s32(src_width), s32(src_height)}, }; const Region2D dst_region{ .start = {0, 0}, - .end = {static_cast(dst_width), static_cast(dst_height)}, + .end = {s32(dst_width), s32(dst_height)}, }; const VkExtent2D extent{ .width = (std::max)(scaled_width, info.size.width), @@ -1995,21 +2067,15 @@ bool Image::BlitScaleHelper(bool scale_up) { auto* view_ptr = blit_view.get(); if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { - if (!blit_framebuffer) { - blit_framebuffer = - std::make_unique(*runtime, view_ptr, nullptr, extent, scale_up); - } - - runtime->blit_image_helper.BlitColor(blit_framebuffer.get(), *blit_view, dst_region, - src_region, operation, BLIT_OPERATION); + if (!blit_framebuffer) + blit_framebuffer.emplace(*runtime, view_ptr, nullptr, extent, scale_up); + runtime->blit_image_helper.BlitColor(&*blit_framebuffer, *blit_view, + dst_region, src_region, operation, BLIT_OPERATION); } else if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { - if (!blit_framebuffer) { - blit_framebuffer = - std::make_unique(*runtime, nullptr, view_ptr, extent, scale_up); - } - runtime->blit_image_helper.BlitDepthStencil(blit_framebuffer.get(), *blit_view, - dst_region, src_region, operation, - BLIT_OPERATION); + if (!blit_framebuffer) + blit_framebuffer.emplace(*runtime, nullptr, view_ptr, extent, scale_up); + runtime->blit_image_helper.BlitDepthStencil(&*blit_framebuffer, *blit_view, + dst_region, src_region, operation, BLIT_OPERATION); } else { // TODO: Use helper blits where applicable flags &= ~ImageFlagBits::Rescaled; @@ -2049,7 +2115,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI }; if (!info.IsRenderTarget()) { swizzle = info.Swizzle(); - TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(), + TryTransformSwizzleIfNeeded(format, swizzle, !device->IsExt4444FormatsSupported()); if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); @@ -2122,9 +2188,9 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI } } -ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, - ImageId image_id_, Image& image, const SlotVector& slot_imgs) - : ImageView{runtime, info, image_id_, image} { +ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, ImageId image_id_, Image& image, const SlotVector& slot_imgs) + : ImageView{runtime, info, image_id_, image} +{ slot_images = &slot_imgs; } @@ -2189,33 +2255,25 @@ VkImageView ImageView::ColorView() { VkImageView ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) { - if (!image_handle) { - return VK_NULL_HANDLE; - } - if (image_format == Shader::ImageFormat::Typeless) { - return Handle(texture_type); - } - const bool is_signed{image_format == Shader::ImageFormat::R8_SINT || - image_format == Shader::ImageFormat::R16_SINT}; - if (!storage_views) { - storage_views = std::make_unique(); - } - auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds}; - auto& view{views[static_cast(texture_type)]}; - if (view) { + if (image_handle) { + if (image_format == Shader::ImageFormat::Typeless) { + return Handle(texture_type); + } + const bool is_signed = image_format == Shader::ImageFormat::R8_SINT + || image_format == Shader::ImageFormat::R16_SINT; + if (!storage_views) + storage_views.emplace(); + auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds}; + auto& view{views[size_t(texture_type)]}; + if (!view) + view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT); return *view; } - view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT); - return *view; + return VK_NULL_HANDLE; } bool ImageView::IsRescaled() const noexcept { - if (!slot_images) { - return false; - } - const auto& slots = *slot_images; - const auto& src_image = slots[image_id]; - return src_image.IsRescaled(); + return (*slot_images)[image_id].IsRescaled(); } vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) { @@ -2403,10 +2461,22 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, void TextureCacheRuntime::AccelerateImageUpload( Image& image, const StagingBufferRef& map, - std::span swizzles) { + std::span swizzles, + u32 z_start, u32 z_count) { + if (IsPixelFormatASTC(image.info.format)) { return astc_decoder_pass->Assemble(image, map, swizzles); } + + if (bl3d_unswizzle_pass && + IsPixelFormatBCn(image.info.format) && + image.info.type == ImageType::e3D && + image.info.resources.levels == 1 && + image.info.resources.layers == 1) { + + return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); + } + ASSERT(false); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 570a3cb335..4bb9687ab0 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -51,7 +51,7 @@ public: void Finish(); - StagingBufferRef UploadStagingBuffer(size_t size); + StagingBufferRef UploadStagingBuffer(size_t size, bool deferred = false); StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); @@ -91,7 +91,8 @@ public: } void AccelerateImageUpload(Image&, const StagingBufferRef&, - std::span); + std::span, + u32 z_start, u32 z_count); void InsertUploadMemoryBarrier() {} @@ -127,7 +128,12 @@ public: BlitImageHelper& blit_image_helper; RenderPassCache& render_pass_cache; std::optional astc_decoder_pass; - std::unique_ptr msaa_copy_pass; + + std::optional bl3d_unswizzle_pass; + vk::Buffer swizzle_table_buffer; + VkDeviceSize swizzle_table_size = 0; + + std::optional msaa_copy_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; @@ -135,184 +141,6 @@ public: std::array buffers{}; }; -class Image : public VideoCommon::ImageBase { -public: - explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, - VAddr cpu_addr); - explicit Image(const VideoCommon::NullImageParams&); - - ~Image(); - - Image(const Image&) = delete; - Image& operator=(const Image&) = delete; - - Image(Image&&) = default; - Image& operator=(Image&&) = default; - - void UploadMemory(VkBuffer buffer, VkDeviceSize offset, - std::span copies); - - void UploadMemory(const StagingBufferRef& map, - std::span copies); - - void DownloadMemory(VkBuffer buffer, size_t offset, - std::span copies); - - void DownloadMemory(std::span buffers, std::span offsets, - std::span copies); - - void DownloadMemory(const StagingBufferRef& map, - std::span copies); - - [[nodiscard]] VkImage Handle() const noexcept { - return *(this->*current_image); - } - - [[nodiscard]] VkImageAspectFlags AspectMask() const noexcept { - return aspect_mask; - } - - [[nodiscard]] VkImageUsageFlags UsageFlags() const noexcept { - return (this->*current_image).UsageFlags(); - } - - /// Returns true when the image is already initialized and mark it as initialized - [[nodiscard]] bool ExchangeInitialization() noexcept { - return std::exchange(initialized, true); - } - - VkImageView StorageImageView(s32 level) noexcept; - - bool IsRescaled() const noexcept; - - bool ScaleUp(bool ignore = false); - - bool ScaleDown(bool ignore = false); - -private: - bool BlitScaleHelper(bool scale_up); - - bool NeedsScaleHelper() const; - - Scheduler* scheduler{}; - TextureCacheRuntime* runtime{}; - - vk::Image original_image; - vk::Image scaled_image; - - // Use a pointer to field because it is relative, so that the object can be - // moved without breaking the reference. - vk::Image Image::*current_image{}; - - std::vector storage_image_views; - VkImageAspectFlags aspect_mask = 0; - bool initialized = false; - - std::unique_ptr scale_framebuffer; - std::unique_ptr scale_view; - - std::unique_ptr normal_framebuffer; - std::unique_ptr normal_view; -}; - -class ImageView : public VideoCommon::ImageViewBase { -public: - explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&); - explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&, - const SlotVector&); - explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&, - const VideoCommon::ImageViewInfo&, GPUVAddr); - explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams&); - - ~ImageView(); - - ImageView(const ImageView&) = delete; - ImageView& operator=(const ImageView&) = delete; - - ImageView(ImageView&&) = default; - ImageView& operator=(ImageView&&) = default; - - [[nodiscard]] VkImageView DepthView(); - - [[nodiscard]] VkImageView StencilView(); - - [[nodiscard]] VkImageView ColorView(); - - [[nodiscard]] VkImageView StorageView(Shader::TextureType texture_type, - Shader::ImageFormat image_format); - - [[nodiscard]] bool IsRescaled() const noexcept; - - [[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept { - return *image_views[static_cast(texture_type)]; - } - - [[nodiscard]] VkImage ImageHandle() const noexcept { - return image_handle; - } - - [[nodiscard]] VkImageView RenderTarget() const noexcept { - return render_target; - } - - [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept { - return samples; - } - - [[nodiscard]] GPUVAddr GpuAddr() const noexcept { - return gpu_addr; - } - - [[nodiscard]] u32 BufferSize() const noexcept { - return buffer_size; - } - -private: - struct StorageViews { - std::array signeds; - std::array unsigneds; - }; - - [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); - - const Device* device = nullptr; - const SlotVector* slot_images = nullptr; - - std::array image_views; - std::unique_ptr storage_views; - vk::ImageView depth_view; - vk::ImageView stencil_view; - vk::ImageView color_view; - vk::Image null_image; - VkImage image_handle = VK_NULL_HANDLE; - VkImageView render_target = VK_NULL_HANDLE; - VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; - u32 buffer_size = 0; -}; - -class ImageAlloc : public VideoCommon::ImageAllocBase {}; - -class Sampler { -public: - explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); - - [[nodiscard]] VkSampler Handle() const noexcept { - return *sampler; - } - - [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { - return *sampler_default_anisotropy; - } - - [[nodiscard]] bool HasAddedAnisotropy() const noexcept { - return static_cast(sampler_default_anisotropy); - } - -private: - vk::Sampler sampler; - vk::Sampler sampler_default_anisotropy; -}; - class Framebuffer { public: explicit Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, @@ -396,6 +224,195 @@ private: bool is_rescaled{}; }; +class Image : public VideoCommon::ImageBase { +public: + explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, + VAddr cpu_addr); + explicit Image(const VideoCommon::NullImageParams&); + + ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + Image(Image&&) = default; + Image& operator=(Image&&) = default; + + void UploadMemory(VkBuffer buffer, VkDeviceSize offset, + std::span copies); + + void UploadMemory(const StagingBufferRef& map, + std::span copies); + + void DownloadMemory(VkBuffer buffer, size_t offset, + std::span copies); + + void DownloadMemory(std::span buffers, std::span offsets, + std::span copies); + + void DownloadMemory(const StagingBufferRef& map, + std::span copies); + + void AllocateComputeUnswizzleImage(); + + [[nodiscard]] VkImage Handle() const noexcept { + return *(this->*current_image); + } + + [[nodiscard]] VkImageAspectFlags AspectMask() const noexcept { + return aspect_mask; + } + + [[nodiscard]] VkImageUsageFlags UsageFlags() const noexcept { + return (this->*current_image).UsageFlags(); + } + + /// Returns true when the image is already initialized and mark it as initialized + [[nodiscard]] bool ExchangeInitialization() noexcept { + return std::exchange(initialized, true); + } + + VkImageView StorageImageView(s32 level) noexcept; + + bool IsRescaled() const noexcept; + + bool ScaleUp(bool ignore = false); + + bool ScaleDown(bool ignore = false); + + u64 allocation_tick; + + friend class BlockLinearUnswizzle3DPass; + +private: + bool BlitScaleHelper(bool scale_up); + + bool NeedsScaleHelper() const; + + Scheduler* scheduler{}; + TextureCacheRuntime* runtime{}; + + vk::Image original_image; + vk::Image scaled_image; + + vk::Buffer compute_unswizzle_buffer; + VkDeviceSize compute_unswizzle_buffer_size = 0; + bool has_compute_unswizzle_buffer = false; + + void AllocateComputeUnswizzleBuffer(u32 max_slices); + + // Use a pointer to field because it is relative, so that the object can be + // moved without breaking the reference. + vk::Image Image::*current_image{}; + + std::vector storage_image_views; + VkImageAspectFlags aspect_mask = 0; + bool initialized = false; + + std::optional scale_framebuffer; + std::optional normal_framebuffer; + std::unique_ptr scale_view; + std::unique_ptr normal_view; +}; + +class ImageView : public VideoCommon::ImageViewBase { +public: + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&, + const SlotVector&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&, + const VideoCommon::ImageViewInfo&, GPUVAddr); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams&); + + ~ImageView(); + + ImageView(const ImageView&) = delete; + ImageView& operator=(const ImageView&) = delete; + + ImageView(ImageView&&) = default; + ImageView& operator=(ImageView&&) = default; + + [[nodiscard]] VkImageView DepthView(); + + [[nodiscard]] VkImageView StencilView(); + + [[nodiscard]] VkImageView ColorView(); + + [[nodiscard]] VkImageView StorageView(Shader::TextureType texture_type, + Shader::ImageFormat image_format); + + [[nodiscard]] bool IsRescaled() const noexcept; + + [[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept { + return *image_views[static_cast(texture_type)]; + } + + [[nodiscard]] VkImage ImageHandle() const noexcept { + return image_handle; + } + + [[nodiscard]] VkImageView RenderTarget() const noexcept { + return render_target; + } + + [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept { + return samples; + } + + [[nodiscard]] GPUVAddr GpuAddr() const noexcept { + return gpu_addr; + } + + [[nodiscard]] u32 BufferSize() const noexcept { + return buffer_size; + } + +private: + struct StorageViews { + std::array signeds; + std::array unsigneds; + }; + + [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); + + const Device* device = nullptr; + const SlotVector* slot_images = nullptr; + + std::array image_views; + std::optional storage_views; + vk::ImageView depth_view; + vk::ImageView stencil_view; + vk::ImageView color_view; + vk::Image null_image; + VkImage image_handle = VK_NULL_HANDLE; + VkImageView render_target = VK_NULL_HANDLE; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; + u32 buffer_size = 0; +}; + +class ImageAlloc : public VideoCommon::ImageAllocBase {}; + +class Sampler { +public: + explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); + + [[nodiscard]] VkSampler Handle() const noexcept { + return *sampler; + } + + [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { + return *sampler_default_anisotropy; + } + + [[nodiscard]] bool HasAddedAnisotropy() const noexcept { + return static_cast(sampler_default_anisotropy); + } + +private: + vk::Sampler sampler; + vk::Sampler sampler_default_anisotropy; +}; + struct TextureCacheParams { static constexpr bool ENABLE_VALIDATION = true; static constexpr bool FRAMEBUFFER_BLITS = false; diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index de12d795c8..85cdb948f3 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h index 95c2d79277..c5bd0e7339 100644 --- a/src/video_core/shader_environment.h +++ b/src/video_core/shader_environment.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c580fb10ef..2564a67780 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -22,6 +23,7 @@ #include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/texture_cache_base.h" #include "video_core/texture_cache/util.h" +#include "video_core/textures/decoders.h" namespace VideoCommon { @@ -68,10 +70,41 @@ TextureCache

::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag (std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical), DEFAULT_CRITICAL_MEMORY)); minimum_memory = static_cast((device_local_memory - mem_threshold) / 2); + + lowmemorydevice = false; } else { expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB; critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB; minimum_memory = 0; + + lowmemorydevice = true; + } + + switch (Settings::values.gpu_unswizzle_texture_size.GetValue()) { + case Settings::GpuUnswizzleSize::VerySmall: gpu_unswizzle_maxsize = 16_MiB; break; + case Settings::GpuUnswizzleSize::Small: gpu_unswizzle_maxsize = 32_MiB; break; + case Settings::GpuUnswizzleSize::Normal: gpu_unswizzle_maxsize = 128_MiB; break; + case Settings::GpuUnswizzleSize::Large: gpu_unswizzle_maxsize = 256_MiB; break; + case Settings::GpuUnswizzleSize::VeryLarge: gpu_unswizzle_maxsize = 512_MiB; break; + default: gpu_unswizzle_maxsize = 128_MiB; break; + } + + switch (Settings::values.gpu_unswizzle_stream_size.GetValue()) { + case Settings::GpuUnswizzle::VeryLow: swizzle_chunk_size = 4_MiB; break; + case Settings::GpuUnswizzle::Low: swizzle_chunk_size = 8_MiB; break; + case Settings::GpuUnswizzle::Normal: swizzle_chunk_size = 16_MiB; break; + case Settings::GpuUnswizzle::Medium: swizzle_chunk_size = 32_MiB; break; + case Settings::GpuUnswizzle::High: swizzle_chunk_size = 64_MiB; break; + default: swizzle_chunk_size = 16_MiB; + } + + switch (Settings::values.gpu_unswizzle_chunk_size.GetValue()) { + case Settings::GpuUnswizzleChunk::VeryLow: swizzle_slices_per_batch = 32; break; + case Settings::GpuUnswizzleChunk::Low: swizzle_slices_per_batch = 64; break; + case Settings::GpuUnswizzleChunk::Normal: swizzle_slices_per_batch = 128; break; + case Settings::GpuUnswizzleChunk::Medium: swizzle_slices_per_batch = 256; break; + case Settings::GpuUnswizzleChunk::High: swizzle_slices_per_batch = 512; break; + default: swizzle_slices_per_batch = 128; } } @@ -88,6 +121,7 @@ void TextureCache

::RunGarbageCollector() { ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL; num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10); }; + const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) { if (num_iterations == 0) { @@ -95,20 +129,36 @@ void TextureCache

::RunGarbageCollector() { } --num_iterations; auto& image = slot_images[image_id]; + + // Never delete recently allocated sparse textures (within 3 frames) + const bool is_recently_allocated = image.allocation_tick >= frame_tick - 3; + if (is_recently_allocated && image.info.is_sparse) { + return false; + } + if (True(image.flags & ImageFlagBits::IsDecoding)) { // This image is still being decoded, deleting it will invalidate the slot // used by the async decoder thread. return false; } - if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) { + + // Prioritize large sparse textures for cleanup + const bool is_large_sparse = lowmemorydevice && + image.info.is_sparse && + image.guest_size_bytes >= 256_MiB; + + if (!aggressive_mode && !is_large_sparse && + True(image.flags & ImageFlagBits::CostlyLoad)) { return false; } + const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); - if (!high_priority_mode && must_download) { + if (!high_priority_mode && !is_large_sparse && must_download) { return false; } - if (must_download) { + + if (must_download && !is_large_sparse) { auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); image.DownloadMemory(map, copies); @@ -116,11 +166,13 @@ void TextureCache

::RunGarbageCollector() { SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer); } + if (True(image.flags & ImageFlagBits::Tracked)) { UntrackImage(image, image_id); } UnregisterImage(image_id); DeleteImage(image_id, image.scale_tick > frame_tick + 5); + if (total_used_memory < critical_memory) { if (aggressive_mode) { // Sink the aggresiveness. @@ -136,7 +188,24 @@ void TextureCache

::RunGarbageCollector() { return false; }; - // Try to remove anything old enough and not high priority. + // Aggressively clear massive sparse textures + if (total_used_memory >= expected_memory) { + lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) { + auto& image = slot_images[image_id]; + // Only target sparse textures that are old enough + if (lowmemorydevice && + image.info.is_sparse && + image.guest_size_bytes >= 256_MiB && + image.allocation_tick < frame_tick - 3) { + LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)", + image.gpu_addr, image.guest_size_bytes / (1024 * 1024), + frame_tick - image.allocation_tick); + return Cleanup(image_id); + } + return false; + }); + } + Configure(false); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); @@ -160,6 +229,7 @@ void TextureCache

::TickFrame() { sentenced_framebuffers.Tick(); sentenced_image_view.Tick(); TickAsyncDecode(); + TickAsyncUnswizzle(); runtime.TickFrame(); ++frame_tick; @@ -218,21 +288,41 @@ void TextureCache

::CheckFeedbackLoop(std::span views) { if (!view.id) { continue; } - auto& image_view = slot_image_views[view.id]; - // Check color targets + bool is_render_target = false; + for (const auto& ct_view_id : render_targets.color_buffer_ids) { - if (ct_view_id) { - auto& ct_view = slot_image_views[ct_view_id]; - if (image_view.image_id == ct_view.image_id) { - return true; - } + if (ct_view_id && ct_view_id == view.id) { + is_render_target = true; + break; + } + } + + if (!is_render_target && render_targets.depth_buffer_id == view.id) { + is_render_target = true; + } + + if (is_render_target) { + continue; + } + + auto& image_view = slot_image_views[view.id]; + + for (const auto& ct_view_id : render_targets.color_buffer_ids) { + if (!ct_view_id) { + continue; + } + + auto& ct_view = slot_image_views[ct_view_id]; + + if (image_view.image_id == ct_view.image_id) { + return true; } } - // Check zeta target if (render_targets.depth_buffer_id) { auto& zt_view = slot_image_views[render_targets.depth_buffer_id]; + if (image_view.image_id == zt_view.image_id) { return true; } @@ -526,10 +616,10 @@ FramebufferId TextureCache

::GetFramebufferId(const RenderTargets& key) { return framebuffer_id; } std::array color_buffers; - std::ranges::transform(key.color_buffer_ids, color_buffers.begin(), - [this](ImageViewId id) { return id ? &slot_image_views[id] : nullptr; }); - ImageView* const depth_buffer = - key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr; + std::ranges::transform(key.color_buffer_ids, color_buffers.begin(), [this](ImageViewId id) { + return id ? &slot_image_views[id] : nullptr; + }); + ImageView* const depth_buffer = key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr; framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key); return framebuffer_id; } @@ -627,7 +717,6 @@ void TextureCache

::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz UntrackImage(image, id); } } - if (True(image.flags & ImageFlagBits::Remapped)) { continue; } @@ -1055,7 +1144,12 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { // Only upload modified images return; } + image.flags &= ~ImageFlagBits::CpuModified; + if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) { + return; + } + TrackImage(image, image_id); if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { @@ -1067,6 +1161,16 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { QueueAsyncDecode(image, image_id); return; } + if (IsPixelFormatBCn(image.info.format) && + image.info.type == ImageType::e3D && + image.info.resources.levels == 1 && + image.info.resources.layers == 1 && + MapSizeBytes(image) >= gpu_unswizzle_maxsize && + False(image.flags & ImageFlagBits::GpuModified)) { + + QueueAsyncUnswizzle(image, image_id); + return; + } auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image)); UploadImageContents(image, staging); runtime.InsertUploadMemoryBarrier(); @@ -1082,7 +1186,7 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(), VideoCommon::CacheType::NoTextureCache); const auto uploads = FullUploadSwizzles(image.info); - runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads)); + runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0); return; } @@ -1311,6 +1415,20 @@ void TextureCache

::QueueAsyncDecode(Image& image, ImageId image_id) { texture_decode_worker.QueueWork(std::move(func)); } +template +void TextureCache

::QueueAsyncUnswizzle(Image& image, ImageId image_id) { + if (True(image.flags & ImageFlagBits::IsDecoding)) { + return; + } + + image.flags |= ImageFlagBits::IsDecoding; + + unswizzle_queue.push_back({ + .image_id = image_id, + .info = image.info + }); +} + template void TextureCache

::TickAsyncDecode() { bool has_uploads{}; @@ -1336,6 +1454,83 @@ void TextureCache

::TickAsyncDecode() { } } +template +void TextureCache

::TickAsyncUnswizzle() { + if (unswizzle_queue.empty()) { + return; + } + + if(current_unswizzle_frame > 0) { + current_unswizzle_frame--; + return; + } + + PendingUnswizzle& task = unswizzle_queue.front(); + Image& image = slot_images[task.image_id]; + + if (!task.initialized) { + task.total_size = MapSizeBytes(image); + task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true); + + const auto& info = image.info; + const u32 bytes_per_block = BytesPerBlock(info.format); + const u32 width_blocks = Common::DivCeil(info.size.width, 4u); + const u32 height_blocks = Common::DivCeil(info.size.height, 4u); + + const u32 stride = width_blocks * bytes_per_block; + const u32 aligned_height = height_blocks; + task.bytes_per_slice = static_cast(stride) * aligned_height; + task.last_submitted_offset = 0; + task.initialized = true; + } + + // Read data + if (task.current_offset < task.total_size) { + const size_t remaining = task.total_size - task.current_offset; + + size_t copy_amount = (std::min)(swizzle_chunk_size, remaining); + + if (remaining > swizzle_chunk_size) { + copy_amount = (copy_amount / task.bytes_per_slice) * task.bytes_per_slice; + if (copy_amount == 0) copy_amount = task.bytes_per_slice; + } + + gpu_memory->ReadBlock(image.gpu_addr + task.current_offset, + task.staging_buffer.mapped_span.data() + task.current_offset, + copy_amount); + task.current_offset += copy_amount; + } + + const bool is_final_batch = task.current_offset >= task.total_size; + const size_t bytes_ready = task.current_offset - task.last_submitted_offset; + const u32 complete_slices = static_cast(bytes_ready / task.bytes_per_slice); + + if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) { + const u32 z_start = static_cast(task.last_submitted_offset / task.bytes_per_slice); + const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch); + const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start); + + if (z_count > 0) { + const auto uploads = FullUploadSwizzles(task.info); + runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), z_start, z_count); + task.last_submitted_offset += (static_cast(z_count) * task.bytes_per_slice); + } + } + + // Check if complete + const u32 slices_submitted = static_cast(task.last_submitted_offset / task.bytes_per_slice); + const bool all_slices_submitted = slices_submitted >= image.info.size.depth; + + if (is_final_batch && all_slices_submitted) { + runtime.FreeDeferredStagingBuffer(task.staging_buffer); + image.flags &= ~ImageFlagBits::IsDecoding; + unswizzle_queue.pop_front(); + + // Wait 4 frames to process the next entry + current_unswizzle_frame = 4u; + } +} + template bool TextureCache

::ScaleUp(Image& image) { const bool has_copy = image.HasScaled(); @@ -1374,6 +1569,39 @@ ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, } } ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); + + // For large sparse textures, aggressively clean up old allocations at same address + if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) { + const auto alloc_it = image_allocs_table.find(gpu_addr); + if (alloc_it != image_allocs_table.end()) { + const ImageAllocId alloc_id = alloc_it->second; + auto& alloc_images = slot_image_allocs[alloc_id].images; + + // Collect old images at this address that were created more than 2 frames ago + boost::container::small_vector to_delete; + for (ImageId old_image_id : alloc_images) { + Image& old_image = slot_images[old_image_id]; + if (old_image.info.is_sparse && + old_image.gpu_addr == gpu_addr && + old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures + to_delete.push_back(old_image_id); + } + } + + // Delete old images immediately + for (ImageId old_id : to_delete) { + Image& old_image = slot_images[old_id]; + LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)", + gpu_addr, old_image.guest_size_bytes / (1024 * 1024)); + if (True(old_image.flags & ImageFlagBits::Tracked)) { + UntrackImage(old_image, old_id); + } + UnregisterImage(old_id); + DeleteImage(old_id, true); + } + } + } + const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different @@ -1389,6 +1617,27 @@ template ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) { ImageInfo new_info = info; const size_t size_bytes = CalculateGuestSizeInBytes(new_info); + + // Proactive cleanup for large sparse texture allocations + if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) { + const u64 estimated_alloc_size = size_bytes; + + if (total_used_memory + estimated_alloc_size >= critical_memory) { + LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. " + "Current memory: {} MiB, Critical: {} MiB", + size_bytes / (1024 * 1024), + total_used_memory / (1024 * 1024), + critical_memory / (1024 * 1024)); + RunGarbageCollector(); + + // If still over threshold after GC, try one more aggressive pass + if (total_used_memory + estimated_alloc_size >= critical_memory) { + LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass"); + RunGarbageCollector(); + } + } + } + const bool broken_views = runtime.HasBrokenTextureViewFormats(); const bool native_bgr = runtime.HasNativeBgr(); join_overlap_ids.clear(); @@ -1485,6 +1734,8 @@ ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr); Image& new_image = slot_images[new_image_id]; + new_image.allocation_tick = frame_tick; + if (!gpu_memory->IsContinuousRange(new_image.gpu_addr, new_image.guest_size_bytes) && new_info.is_sparse) { new_image.flags |= ImageFlagBits::Sparse; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 5146a8c291..42f1a158d9 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -129,6 +129,17 @@ class TextureCache : public VideoCommon::ChannelSetupCaches QuerySamplerBudget() const; + void QueueAsyncUnswizzle(Image& image, ImageId image_id); + void TickAsyncUnswizzle(); + Runtime& runtime; Tegra::MaxwellDeviceMemoryManager& device_memory; @@ -453,6 +467,10 @@ private: u64 minimum_memory; u64 expected_memory; u64 critical_memory; + bool lowmemorydevice = false; + size_t gpu_unswizzle_maxsize = 0; + size_t swizzle_chunk_size = 0; + u32 swizzle_slices_per_batch = 0; struct BufferDownload { GPUVAddr address; @@ -508,6 +526,9 @@ private: Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"}; std::vector> async_decodes; + std::deque unswizzle_queue; + u8 current_unswizzle_frame; + // Join caching boost::container::small_vector join_overlap_ids; std::unordered_set join_overlaps_found; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 64a32cf504..25b0860b0f 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -18,7 +18,8 @@ namespace Tegra::Texture { namespace { -constexpr u32 pdep(u32 mask, u32 value) { +template +constexpr u32 pdep(u32 value) { u32 result = 0; u32 m = mask; for (u32 bit = 1; m; bit += bit) { @@ -29,12 +30,15 @@ constexpr u32 pdep(u32 mask, u32 value) { return result; } -void incrpdep(u32 mask, u32 incr_amount, u32& value) { - u32 swizzled_incr = pdep(mask, incr_amount); +template +void incrpdep(u32& value) { + static constexpr u32 swizzled_incr = pdep(incr_amount); value = ((value | ~mask) + swizzled_incr) & mask; } -void SwizzleImpl(bool to_linear, u32 bytes_per_pixel, std::span output, std::span input, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride) { +template +void SwizzleImpl(std::span output, std::span input, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth, u32 stride) { // 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; @@ -42,12 +46,13 @@ void SwizzleImpl(bool to_linear, u32 bytes_per_pixel, std::span output, std: static constexpr u32 origin_z = 0; // We can configure here a custom pitch - // As it's not exposed 'width * bytes_per_pixel' will be the expected pitch. - const u32 pitch = width * 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 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); - const u32 slice_size = Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size; + const u32 slice_size = + Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size; const u32 block_height_mask = (1U << block_height) - 1; const u32 block_depth_mask = (1U << block_depth) - 1; @@ -59,42 +64,50 @@ void SwizzleImpl(bool to_linear, u32 bytes_per_pixel, std::span output, std: ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height)); for (u32 line = 0; line < height; ++line) { const u32 y = line + origin_y; - const u32 swizzled_y = pdep(SWIZZLE_Y_BITS, y); + const u32 swizzled_y = pdep(y); const u32 block_y = y >> GOB_SIZE_Y_SHIFT; - const u32 offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); + const u32 offset_y = (block_y >> block_height) * block_size + + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); - u32 swizzled_x = pdep(SWIZZLE_X_BITS, origin_x * bytes_per_pixel); - for (u32 column = 0; column < width; ++column, incrpdep(SWIZZLE_X_BITS, bytes_per_pixel, swizzled_x)) { - const u32 x = (column + origin_x) * bytes_per_pixel; + u32 swizzled_x = pdep(origin_x * BYTES_PER_PIXEL); + for (u32 column = 0; column < width; + ++column, incrpdep(swizzled_x)) { + 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 + (swizzled_x | swizzled_y); - const u32 unswizzled_offset = slice * pitch * height + line * pitch + column * bytes_per_pixel; - u8* const dst = &output[to_linear ? swizzled_offset : unswizzled_offset]; - const u8* const src = &input[to_linear ? unswizzled_offset : swizzled_offset]; + const u32 unswizzled_offset = + slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; - std::memcpy(dst, src, bytes_per_pixel); + 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); } } } } -void SwizzleSubrectImpl(bool to_linear, u32 bytes_per_pixel, std::span output, std::span input, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 num_lines, u32 block_height, u32 block_depth, u32 pitch_linear) { +template +void SwizzleSubrectImpl(std::span output, std::span input, u32 width, u32 height, + u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 num_lines, + u32 block_height, u32 block_depth, u32 pitch_linear) { // 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_z = 0; // We can configure here a custom pitch - // As it's not exposed 'width * bytes_per_pixel' will be the expected pitch. + // As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch. const u32 pitch = pitch_linear; - const u32 stride = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT); + const u32 stride = Common::AlignUpLog2(width * BYTES_PER_PIXEL, GOB_SIZE_X_SHIFT); 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); - const u32 slice_size = Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size; + const u32 slice_size = + Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size; const u32 block_height_mask = (1U << block_height) - 1; const u32 block_depth_mask = (1U << block_depth) - 1; @@ -110,25 +123,28 @@ void SwizzleSubrectImpl(bool to_linear, u32 bytes_per_pixel, std::span outpu const u32 lines_in_y = (std::min)(unprocessed_lines, extent_y); for (u32 line = 0; line < lines_in_y; ++line) { const u32 y = line + origin_y; - const u32 swizzled_y = pdep(SWIZZLE_Y_BITS, y); + const u32 swizzled_y = pdep(y); const u32 block_y = y >> GOB_SIZE_Y_SHIFT; - const u32 offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); + const u32 offset_y = (block_y >> block_height) * block_size + + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); - u32 swizzled_x = pdep(SWIZZLE_X_BITS, origin_x * bytes_per_pixel); - for (u32 column = 0; column < extent_x; ++column, incrpdep(SWIZZLE_X_BITS, bytes_per_pixel, swizzled_x)) { - const u32 x = (column + origin_x) * bytes_per_pixel; + u32 swizzled_x = pdep(origin_x * BYTES_PER_PIXEL); + for (u32 column = 0; column < extent_x; + ++column, incrpdep(swizzled_x)) { + 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 + (swizzled_x | swizzled_y); - const u32 unswizzled_offset = slice * pitch * height + line * pitch + column * bytes_per_pixel; + const u32 unswizzled_offset = + slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; - u8* const dst = &output[to_linear ? swizzled_offset : unswizzled_offset]; - const u8* const src = &input[to_linear ? unswizzled_offset : swizzled_offset]; + 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); } } unprocessed_lines -= lines_in_y; @@ -138,17 +154,23 @@ void SwizzleSubrectImpl(bool to_linear, u32 bytes_per_pixel, std::span outpu } } -void Swizzle(bool to_linear, std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { +template +void Swizzle(std::span output, std::span input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { switch (bytes_per_pixel) { - case 1: - case 2: - case 3: - case 4: - case 6: - case 8: - case 12: - case 16: - return SwizzleImpl(to_linear, bytes_per_pixel, output, input, width, height, depth, block_height, block_depth, stride_alignment); +#define BPP_CASE(x) \ + case x: \ + return SwizzleImpl(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: ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); break; @@ -157,57 +179,78 @@ void Swizzle(bool to_linear, std::span output, std::span input, u3 } // Anonymous namespace -void UnswizzleTexture(std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { +void UnswizzleTexture(std::span output, std::span input, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; - const u32 new_bpp = (std::min)(4U, u32(std::countr_zero(width * bytes_per_pixel))); + const u32 new_bpp = (std::min)(4U, static_cast(std::countr_zero(width * bytes_per_pixel))); width = (width * bytes_per_pixel) >> new_bpp; bytes_per_pixel = 1U << new_bpp; - Swizzle(false, output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, stride); + Swizzle(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride); } -void SwizzleTexture(std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { +void SwizzleTexture(std::span output, std::span input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; - const u32 new_bpp = (std::min)(4U, u32(std::countr_zero(width * bytes_per_pixel))); + const u32 new_bpp = (std::min)(4U, static_cast(std::countr_zero(width * bytes_per_pixel))); width = (width * bytes_per_pixel) >> new_bpp; bytes_per_pixel = 1U << new_bpp; - Swizzle(true, output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, stride); + Swizzle(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride); } -void SwizzleSubrect(std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) { +void SwizzleSubrect(std::span output, std::span input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y, + u32 block_height, u32 block_depth, u32 pitch_linear) { switch (bytes_per_pixel) { - case 1: - case 2: - case 3: - case 4: - case 6: - case 8: - case 12: - case 16: - return SwizzleSubrectImpl(true, bytes_per_pixel, output, input, width, height, depth, origin_x, origin_y, extent_x, extent_y, block_height, block_depth, pitch_linear); +#define BPP_CASE(x) \ + case x: \ + return SwizzleSubrectImpl(output, input, width, height, depth, origin_x, \ + origin_y, extent_x, extent_y, block_height, \ + block_depth, pitch_linear); + 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: ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); break; } } -void UnswizzleSubrect(std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) { +void UnswizzleSubrect(std::span output, std::span input, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, + u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) { switch (bytes_per_pixel) { - case 1: - case 2: - case 3: - case 4: - case 6: - case 8: - case 12: - case 16: - return SwizzleSubrectImpl(false, bytes_per_pixel, output, input, width, height, depth, origin_x, origin_y, extent_x, extent_y, block_height, block_depth, pitch_linear); +#define BPP_CASE(x) \ + case x: \ + return SwizzleSubrectImpl(output, input, width, height, depth, origin_x, \ + origin_y, extent_x, extent_y, block_height, \ + block_depth, pitch_linear); + 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: ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); break; } } -std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { +std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth) { if (tiled) { const u32 aligned_width = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT); const u32 aligned_height = Common::AlignUpLog2(height, GOB_SIZE_Y_SHIFT + block_height); @@ -218,7 +261,8 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height } } -u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height, u32 bytes_per_pixel) { +u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height, + u32 bytes_per_pixel) { auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); }; const u32 gobs_in_block = 1 << block_height; const u32 y_blocks = GOB_SIZE_Y << block_height; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 146f6955bd..4a9751e208 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -22,12 +25,12 @@ std::unique_ptr CreateRenderer( auto& device_memory = system.Host1x().MemoryManager(); switch (Settings::values.renderer_backend.GetValue()) { - case Settings::RendererBackend::OpenGL: - return std::make_unique(emu_window, device_memory, gpu, - std::move(context)); + case Settings::RendererBackend::OpenGL_GLSL: + case Settings::RendererBackend::OpenGL_GLASM: + case Settings::RendererBackend::OpenGL_SPIRV: + return std::make_unique(emu_window, device_memory, gpu, std::move(context)); case Settings::RendererBackend::Vulkan: - return std::make_unique(emu_window, device_memory, gpu, - std::move(context)); + return std::make_unique(emu_window, device_memory, gpu, std::move(context)); case Settings::RendererBackend::Null: return std::make_unique(emu_window, gpu, std::move(context)); default: diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 483a1f2b3a..0b3aea6d2e 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -435,7 +435,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK; const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; - const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; if (!is_suitable) LOG_WARNING(Render_Vulkan, "Unsuitable driver - continuing anyways"); @@ -604,10 +603,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } if (is_qualcomm) { - const u32 version = (properties.properties.driverVersion << 3) >> 3; - if (version < VK_MAKE_API_VERSION(0, 255, 615, 512)) { - has_broken_parallel_compiling = true; - } const size_t sampler_limit = properties.properties.limits.maxSamplerAllocationCount; if (sampler_limit > 0) { constexpr size_t MIN_SAMPLER_BUDGET = 1024U; @@ -648,20 +643,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR has_broken_compute = CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) && !Settings::values.enable_compute_pipelines.GetValue(); - must_emulate_bgr565 = false; // Default: assume emulation isn't required - - if (is_intel_anv) { - LOG_WARNING(Render_Vulkan, "Intel ANV driver does not support native BGR format"); - must_emulate_bgr565 = true; - } else if (is_qualcomm) { - LOG_WARNING(Render_Vulkan, - "Qualcomm driver mishandles BGR5 formats even with VK_KHR_maintenance5, forcing emulation"); - must_emulate_bgr565 = true; - } else if (is_arm) { - LOG_WARNING(Render_Vulkan, - "ARM Mali driver mishandles BGR5 formats even with VK_KHR_maintenance5, forcing emulation"); - must_emulate_bgr565 = true; - } if (is_mvk) { LOG_WARNING(Render_Vulkan, @@ -943,11 +924,7 @@ bool Device::ShouldBoostClocks() const { } bool Device::HasTimelineSemaphore() const { - if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || - GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) { - // Timeline semaphores do not work properly on all Qualcomm drivers. - // They generally work properly with Turnip drivers, but are problematic on some devices - // (e.g. ZTE handsets with Snapdragon 870). + if (GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) { return false; } return features.timeline_semaphore.timelineSemaphore; @@ -1099,9 +1076,14 @@ bool Device::GetSuitability(bool requires_swapchain) { LOG_INFO(Render_Vulkan, "Device doesn't support feature {}", #name); \ } +// Optional features are enabled silently without any logging +#define OPTIONAL_FEATURE(feature, name) (void)features.feature.name; + + FOR_EACH_VK_OPTIONAL_FEATURE(OPTIONAL_FEATURE); FOR_EACH_VK_RECOMMENDED_FEATURE(LOG_FEATURE); FOR_EACH_VK_MANDATORY_FEATURE(CHECK_FEATURE); +#undef OPTIONAL_FEATURE #undef LOG_FEATURE #undef CHECK_FEATURE diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d8e5714931..744b5f827e 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -135,10 +135,6 @@ VK_DEFINE_HANDLE(VmaAllocator) // Define features which must be supported. #define FOR_EACH_VK_MANDATORY_FEATURE(FEATURE_NAME) \ - FEATURE_NAME(bit16_storage, storageBuffer16BitAccess) \ - FEATURE_NAME(bit16_storage, uniformAndStorageBuffer16BitAccess) \ - FEATURE_NAME(bit8_storage, storageBuffer8BitAccess) \ - FEATURE_NAME(bit8_storage, uniformAndStorageBuffer8BitAccess) \ FEATURE_NAME(features, depthBiasClamp) \ FEATURE_NAME(features, depthClamp) \ FEATURE_NAME(features, drawIndirectFirstInstance) \ @@ -171,6 +167,8 @@ VK_DEFINE_HANDLE(VmaAllocator) // Define features where the absence of the feature may result in a degraded experience. #define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ + FEATURE_NAME(bit16_storage, storageBuffer16BitAccess) \ + FEATURE_NAME(bit8_storage, storageBuffer8BitAccess) \ FEATURE_NAME(custom_border_color, customBorderColors) \ FEATURE_NAME(depth_bias_control, depthBiasControl) \ FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \ @@ -191,6 +189,11 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \ FEATURE_NAME(vertex_input_dynamic_state, vertexInputDynamicState) +// These features are not required but can be helpful for drivers that can use it. +#define FOR_EACH_VK_OPTIONAL_FEATURE(FEATURE_NAME) \ + FEATURE_NAME(bit16_storage, uniformAndStorageBuffer16BitAccess) \ + FEATURE_NAME(bit8_storage, uniformAndStorageBuffer8BitAccess) + namespace Vulkan { class NsightAftermathTracker; @@ -793,10 +796,6 @@ public: return must_emulate_scaled_formats; } - bool MustEmulateBGR565() const { - return must_emulate_bgr565; - } - bool HasNullDescriptor() const { return features.robustness2.nullDescriptor; } @@ -896,6 +895,12 @@ public: return extensions.maintenance9; } + /// Returns true if the device supports UINT8 index buffer conversion via compute shader. + bool SupportsUint8Indices() const { + return features.bit8_storage.storageBuffer8BitAccess && + features.bit16_storage.storageBuffer16BitAccess; + } + [[nodiscard]] static constexpr bool CheckBrokenCompute(VkDriverId driver_id, u32 driver_version) { if (driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { @@ -1046,7 +1051,6 @@ private: bool supports_d24_depth{}; ///< Supports D24 depth buffers. bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation - bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. bool dynamic_state3_blending{}; ///< Has blending features of dynamic_state3. bool dynamic_state3_enables{}; ///< Has at least one enable feature of dynamic_state3. bool dynamic_state3_depth_clamp_enable{}; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f3f0939705..35c6f80e40 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later set(CMAKE_AUTOMOC ON) @@ -242,8 +242,7 @@ set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") if (YUZU_CRASH_DUMPS) target_sources(yuzu PRIVATE breakpad.cpp - breakpad.h - ) + breakpad.h) target_link_libraries(yuzu PRIVATE libbreakpad_client) target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) @@ -251,8 +250,7 @@ endif() if (CXX_CLANG) target_compile_definitions(yuzu PRIVATE - $<$,15>:CANNOT_EXPLICITLY_INSTANTIATE> - ) + $<$,15>:CANNOT_EXPLICITLY_INSTANTIATE>) endif() file(GLOB COMPAT_LIST @@ -288,8 +286,7 @@ if (ENABLE_QT_TRANSLATION) ${SRCS} ${UIS} WORKING_DIRECTORY - ${CMAKE_CURRENT_SOURCE_DIR} - ) + ${CMAKE_CURRENT_SOURCE_DIR}) else() qt_create_translation(QM_FILES ${SRCS} @@ -297,8 +294,7 @@ if (ENABLE_QT_TRANSLATION) ${YUZU_QT_LANGUAGES}/en.ts OPTIONS -source-language en_US - -target-language en_US - ) + -target-language en_US) endif() # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts @@ -316,8 +312,7 @@ if (ENABLE_QT_TRANSLATION) ${SRCS} ${UIS} WORKING_DIRECTORY - ${CMAKE_CURRENT_SOURCE_DIR} - ) + ${CMAKE_CURRENT_SOURCE_DIR}) else() qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) endif() @@ -355,8 +350,11 @@ target_sources(yuzu ${COMPAT_LIST} ${ICONS} ${LANGUAGES} - ${THEMES} -) + ${THEMES}) + +if (ENABLE_OPENSSL) + target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto) +endif() if (APPLE) # Normal icns @@ -428,12 +426,6 @@ if (WIN32 AND NOT YUZU_USE_BUNDLED_QT AND QT_VERSION VERSION_GREATER_EQUAL 6) add_custom_command(TARGET yuzu POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${YUZU_EXE_DIR}/eden.exe" --dir "${YUZU_EXE_DIR}" --libdir "${YUZU_EXE_DIR}" --plugindir "${YUZU_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0) endif() -# TODO(crueter): this can be done with system qt in a better way -if (YUZU_USE_BUNDLED_QT) - include(CopyYuzuQt6Deps) - copy_yuzu_Qt6_deps(yuzu) -endif() - if (ENABLE_SDL2) target_link_libraries(yuzu PRIVATE SDL2::SDL2) target_compile_definitions(yuzu PRIVATE HAVE_SDL2) @@ -445,7 +437,6 @@ endif() if (YUZU_ROOM) target_link_libraries(yuzu PRIVATE yuzu-room) -target_link_libraries(yuzu PRIVATE Qt6::Widgets) endif() if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD)) @@ -457,8 +448,9 @@ if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD)) -Wno-missing-field-initializers) endif() -if (YUZU_STATIC_BUILD AND MINGW) - static_qt_link(yuzu) +# Remember that the linker is incredibly stupid. +if (YUZU_STATIC_BUILD AND MINGW AND ARCHITECTURE_x86_64 AND ENABLE_OPENSSL) + target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto) endif() create_target_directory_groups(yuzu) diff --git a/src/yuzu/Info.plist b/src/yuzu/Info.plist index ebf703420c..526b98a820 100644 --- a/src/yuzu/Info.plist +++ b/src/yuzu/Info.plist @@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-2.0-or-later CFBundleIconFile eden.icns CFBundleIconName - Eden + eden_liquidglass CFBundleIdentifier com.yuzu-emu.yuzu CFBundleInfoDictionaryVersion diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index cab8ef190e..3d9d851ab6 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -4,6 +4,11 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/logging/log.h" + #ifdef YUZU_USE_QT_WEB_ENGINE #include @@ -429,15 +434,17 @@ void QtWebBrowser::OpenLocalWebPage(const std::string& local_url, void QtWebBrowser::OpenExternalWebPage(const std::string& external_url, OpenWebPageCallback callback_) const { - callback = std::move(callback_); + LOG_INFO(Service_AM, "Opening external URL in host browser: {}", external_url); - const auto index = external_url.find('?'); + const QUrl url(QString::fromStdString(external_url)); + const bool success = QDesktopServices::openUrl(url); - if (index == std::string::npos) { - emit MainWindowOpenWebPage(external_url, "", false); + if (success) { + LOG_INFO(Service_AM, "Successfully opened URL in host browser"); + callback_(Service::AM::Frontend::WebExitReason::EndButtonPressed, external_url); } else { - emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), - false); + LOG_ERROR(Service_AM, "Failed to open URL in host browser"); + callback_(Service::AM::Frontend::WebExitReason::WindowClosed, external_url); } } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index b6cd7d0985..45cf304644 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -889,12 +889,13 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) { std::unique_ptr GRenderWindow::CreateSharedContext() const { #ifdef HAS_OPENGL - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL + || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM + || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) { auto c = static_cast(main_context.get()); // Bind the shared contexts to the main surface in case the backend wants to take over // presentation - return std::make_unique(c->GetShareContext(), - child_widget->windowHandle()); + return std::make_unique(c->GetShareContext(), child_widget->windowHandle()); } #endif return std::make_unique(); @@ -912,15 +913,15 @@ bool GRenderWindow::InitRenderTarget() { first_frame = false; switch (Settings::values.renderer_backend.GetValue()) { - case Settings::RendererBackend::OpenGL: - if (!InitializeOpenGL()) { + case Settings::RendererBackend::OpenGL_GLSL: + case Settings::RendererBackend::OpenGL_GLASM: + case Settings::RendererBackend::OpenGL_SPIRV: + if (!InitializeOpenGL()) return false; - } break; case Settings::RendererBackend::Vulkan: - if (!InitializeVulkan()) { + if (!InitializeVulkan()) return false; - } break; case Settings::RendererBackend::Null: InitializeNull(); @@ -941,12 +942,10 @@ bool GRenderWindow::InitRenderTarget() { OnFramebufferSizeChanged(); BackupGeometry(); - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { - if (!LoadOpenGL()) { - return false; - } - } - + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL + || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM + || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) + return LoadOpenGL(); return true; } diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 6407efbb26..bc1140d835 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -76,9 +76,9 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } else if (setting->Id() == Settings::values.cpu_backend.Id()) { backend_layout->addWidget(widget); backend_combobox = widget->combobox; - } else if (setting->Id() == Settings::values.fast_cpu_time.Id()) { - ui->general_layout->addWidget(widget); - } else if (setting->Id() == Settings::values.cpu_ticks.Id()) { + } else if (setting->Id() == Settings::values.fast_cpu_time.Id() + || setting->Id() == Settings::values.vtable_bouncing.Id() + || setting->Id() == Settings::values.cpu_ticks.Id()) { ui->general_layout->addWidget(widget); } else { // Presently, all other settings here are unsafe checkboxes diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 76b6153b1a..8663594678 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -91,9 +91,8 @@ ConfigureGraphics::ConfigureGraphics( : ConfigurationShared::Tab(group_, parent), ui{std::make_unique()}, records{records_}, expose_compute_option{expose_compute_option_}, update_aspect_ratio{update_aspect_ratio_}, system{system_}, - combobox_translations{builder.ComboboxTranslations()}, - shader_mapping{ - combobox_translations.at(Settings::EnumMetadata::Index())} { + combobox_translations{builder.ComboboxTranslations()} +{ vulkan_device = Settings::values.vulkan_device.GetValue(); RetrieveVulkanDevices(); @@ -134,9 +133,6 @@ ConfigureGraphics::ConfigureGraphics( UpdateDeviceSelection(device); PopulateVSyncModeSelection(false); }); - connect(shader_backend_combobox, qOverload(&QComboBox::activated), this, - [this](int backend) { UpdateShaderBackendSelection(backend); }); - connect(ui->bg_button, &QPushButton::clicked, this, [this] { const QColor new_bg_color = QColorDialog::getColor(bg_color); if (!new_bg_color.isValid()) { @@ -219,7 +215,9 @@ void ConfigureGraphics::PopulateVSyncModeSelection(bool use_setting) { const Settings::VSyncMode global_vsync_mode = Settings::values.vsync_mode.GetValue(true); vsync_restore_global_button->setEnabled( - (backend == Settings::RendererBackend::OpenGL && + ((backend == Settings::RendererBackend::OpenGL_GLSL + || backend == Settings::RendererBackend::OpenGL_GLASM + || backend == Settings::RendererBackend::OpenGL_SPIRV) && (global_vsync_mode == Settings::VSyncMode::Immediate || global_vsync_mode == Settings::VSyncMode::Fifo)) || backend == Settings::RendererBackend::Vulkan); @@ -246,15 +244,6 @@ void ConfigureGraphics::UpdateDeviceSelection(int device) { } } -void ConfigureGraphics::UpdateShaderBackendSelection(int backend) { - if (backend == -1) { - return; - } - if (GetCurrentGraphicsBackend() == Settings::RendererBackend::OpenGL) { - shader_backend = static_cast(backend); - } -} - ConfigureGraphics::~ConfigureGraphics() = default; void ConfigureGraphics::SetConfiguration() {} @@ -296,14 +285,11 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { api_grid_layout->addWidget(widget); api_combobox = widget->combobox; api_restore_global_button = widget->restore_button; - if (!Settings::IsConfiguringGlobal()) { - api_restore_global_button->connect(api_restore_global_button, &QAbstractButton::clicked, - [this](bool) { UpdateAPILayout(); }); - + api_restore_global_button->connect(api_restore_global_button, &QAbstractButton::clicked, [this](bool) { UpdateAPILayout(); }); // Detach API's restore button and place it where we want // Lets us put it on the side, and it will automatically scale if there's a - // second combobox (shader_backend, vulkan_device) + // second combobox (vulkan_device) widget->layout()->removeWidget(api_restore_global_button); api_layout->addWidget(api_restore_global_button); } @@ -312,11 +298,6 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { hold_api.push_back(widget); vulkan_device_combobox = widget->combobox; vulkan_device_widget = widget; - } else if (setting->Id() == Settings::values.shader_backend.Id()) { - // Keep track of shader_backend's combobox so we can populate it - hold_api.push_back(widget); - shader_backend_combobox = widget->combobox; - shader_backend_widget = widget; } else if (setting->Id() == Settings::values.vsync_mode.Id()) { // Keep track of vsync_mode's combobox so we can populate it vsync_mode_combobox = widget->combobox; @@ -416,20 +397,21 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode, Settings::RendererBackend backend) const { switch (mode) { case VK_PRESENT_MODE_IMMEDIATE_KHR: - return backend == Settings::RendererBackend::OpenGL - ? tr("Off") - : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); + return (backend == Settings::RendererBackend::OpenGL_GLSL + || backend == Settings::RendererBackend::OpenGL_GLASM + || backend == Settings::RendererBackend::OpenGL_SPIRV) + ? tr("Off") : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); case VK_PRESENT_MODE_MAILBOX_KHR: return QStringLiteral("Mailbox (%1)").arg(tr("Recommended")); case VK_PRESENT_MODE_FIFO_KHR: - return backend == Settings::RendererBackend::OpenGL - ? tr("On") - : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); + return (backend == Settings::RendererBackend::OpenGL_GLSL + || backend == Settings::RendererBackend::OpenGL_GLASM + || backend == Settings::RendererBackend::OpenGL_SPIRV) + ? tr("On") : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return QStringLiteral("FIFO Relaxed"); default: return {}; - break; } } @@ -451,7 +433,6 @@ void ConfigureGraphics::ApplyConfiguration() { UpdateVsyncSetting(); Settings::values.vulkan_device.SetGlobal(true); - Settings::values.shader_backend.SetGlobal(true); if (Settings::IsConfiguringGlobal() || (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) { auto backend = static_cast( @@ -460,15 +441,13 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::RendererBackend>::Index())[api_combobox->currentIndex()] .first); switch (backend) { - case Settings::RendererBackend::OpenGL: - Settings::values.shader_backend.SetGlobal(Settings::IsConfiguringGlobal()); - Settings::values.shader_backend.SetValue(static_cast( - shader_mapping[shader_backend_combobox->currentIndex()].first)); - break; case Settings::RendererBackend::Vulkan: Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal()); Settings::values.vulkan_device.SetValue(vulkan_device_combobox->currentIndex()); break; + case Settings::RendererBackend::OpenGL_GLSL: + case Settings::RendererBackend::OpenGL_SPIRV: + case Settings::RendererBackend::OpenGL_GLASM: case Settings::RendererBackend::Null: break; } @@ -501,22 +480,12 @@ void ConfigureGraphics::UpdateAPILayout() { bool runtime_lock = !system.IsPoweredOn(); bool need_global = !(Settings::IsConfiguringGlobal() || api_restore_global_button->isEnabled()); vulkan_device = Settings::values.vulkan_device.GetValue(need_global); - shader_backend = Settings::values.shader_backend.GetValue(need_global); vulkan_device_widget->setEnabled(!need_global && runtime_lock); - shader_backend_widget->setEnabled(!need_global && runtime_lock); const auto current_backend = GetCurrentGraphicsBackend(); - const bool is_opengl = current_backend == Settings::RendererBackend::OpenGL; const bool is_vulkan = current_backend == Settings::RendererBackend::Vulkan; - vulkan_device_widget->setVisible(is_vulkan); - shader_backend_widget->setVisible(is_opengl); - - if (is_opengl) { - shader_backend_combobox->setCurrentIndex( - FindIndex(Settings::EnumMetadata::Index(), - static_cast(shader_backend))); - } else if (is_vulkan && static_cast(vulkan_device) < vulkan_device_combobox->count()) { + if (is_vulkan && int(vulkan_device) < vulkan_device_combobox->count()) { vulkan_device_combobox->setCurrentIndex(vulkan_device); } } @@ -541,15 +510,13 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { return Settings::values.renderer_backend.GetValue(true); } - return static_cast( + return Settings::RendererBackend( combobox_translations.at(Settings::EnumMetadata::Index()) .at(api_combobox->currentIndex()) .first); }(); - if (selected_backend == Settings::RendererBackend::Vulkan && - UISettings::values.has_broken_vulkan) { - return Settings::RendererBackend::OpenGL; - } + if (selected_backend == Settings::RendererBackend::Vulkan && UISettings::values.has_broken_vulkan) + return Settings::RendererBackend::OpenGL_GLSL; return selected_backend; } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 7142b2d36c..3009edf4b6 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -30,7 +30,6 @@ class QComboBox; namespace Settings { enum class NvdecEmulation : u32; enum class RendererBackend : u32; -enum class ShaderBackend : u32; } // namespace Settings namespace Core { @@ -72,7 +71,6 @@ private: void UpdateBackgroundColorButton(QColor color); void UpdateAPILayout(); void UpdateDeviceSelection(int device); - void UpdateShaderBackendSelection(int backend); void RetrieveVulkanDevices(); @@ -97,23 +95,19 @@ private: vsync_mode_combobox_enum_map{}; //< Keeps track of which present mode corresponds to which // selection in the combobox u32 vulkan_device{}; - Settings::ShaderBackend shader_backend{}; const std::function& expose_compute_option; const std::function update_aspect_ratio; const Core::System& system; const ConfigurationShared::ComboboxTranslationMap& combobox_translations; - const std::vector>& shader_mapping; QPushButton* api_restore_global_button; QComboBox* vulkan_device_combobox; QComboBox* api_combobox; - QComboBox* shader_backend_combobox; QComboBox* vsync_mode_combobox; QPushButton* vsync_restore_global_button; QWidget* vulkan_device_widget; QWidget* api_widget; - QWidget* shader_backend_widget; QComboBox* aspect_ratio_combobox; QComboBox* resolution_combobox; }; diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index df74738df4..2b546e1964 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project @@ -23,6 +23,7 @@ #include "common/settings.h" #include "common/string_util.h" #include "common/swap.h" +#include "core/constants.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" @@ -36,16 +37,6 @@ #include "yuzu/configuration/configure_profile_manager.h" namespace { -// Same backup JPEG used by acc IProfile::GetImage if no jpeg found -constexpr std::array backup_jpeg{ - 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, - 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, - 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, - 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, - 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, - 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, - 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, -}; QString GetImagePath(const Common::UUID& uuid) { const auto path = @@ -77,16 +68,15 @@ QPixmap GetIcon(const Common::UUID& uuid) { if (!icon) { icon.fill(Qt::black); - icon.loadFromData(backup_jpeg.data(), static_cast(backup_jpeg.size())); + icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), + static_cast(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); } return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } -QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { - return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"), - description_text, 1, - static_cast(Service::Account::profile_username_size)); +QString GetProfileUsernameFromUser(QWidget* parent, const QString& title, const QString& text) { + return LimitableInputDialog::GetText(parent, title, text, 1, int(Service::Account::profile_username_size)); } } // Anonymous namespace @@ -216,16 +206,21 @@ void ConfigureProfileManager::SelectUser(const QModelIndex& index) { } void ConfigureProfileManager::AddUser() { - const auto username = - GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); - if (username.isEmpty()) { + auto const username = GetProfileUsernameFromUser(this, tr("New Username"), tr("Enter a username:")); + if (username.isEmpty()) return; + + auto const uuid_str = GetProfileUsernameFromUser(this, tr("New User UUID"), tr("Enter a UUID (leave empty to autogenerate):")); + auto uuid = Common::UUID::MakeRandom(); + if (uuid_str.length() > 0) { + if (size_t(uuid_str.length()) != uuid.uuid.size()) + return; + for (size_t i = 0; i < size_t(uuid_str.length()); ++i) + uuid.uuid[i] = u8(uuid_str[i].toLatin1()); } - const auto uuid = Common::UUID::MakeRandom(); profile_manager.CreateNewUser(uuid, username.toStdString()); profile_manager.WriteUserSaveFile(); - item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); } @@ -238,7 +233,7 @@ void ConfigureProfileManager::RenameUser() { if (!profile_manager.GetProfileBase(*uuid, profile)) return; - const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); + const auto new_username = GetProfileUsernameFromUser(this, tr("New Username"), tr("Enter a new username:")); if (new_username.isEmpty()) { return; } diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 4542b63100..a694da8d84 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -400,6 +400,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { for (const auto id : program_ids) { + // dravee suggested this, only viable way to + // not show sub-games in qlaunch for now. + if ((id & 0xFFF) != 0) { + continue; + } loader = Loader::GetLoader(system, file, id); if (!loader) { continue; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index e9f0814ac7..2f968f2b01 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -160,20 +160,19 @@ Am&iibo - - - - + + + + - &Applets + Launch &Applet - - - - - + + + + @@ -420,34 +419,34 @@ &Capture Screenshot - + - Open &Album + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + - Open &Mii Editor + &Mii Editor @@ -493,7 +492,7 @@ R&ecord - + Open &Controller Menu @@ -503,17 +502,9 @@ Install Decryption &Keys - + - Open &Home Menu - - - - - Open &Setup - - - QAction::MenuRole::TextHeuristicRole + &Home Menu diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index c246923fcc..62e410e3c5 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // Qt on macOS doesn't define VMA shit @@ -570,11 +570,9 @@ MainWindow::MainWindow(bool has_broken_vulkan) if (has_broken_vulkan) { UISettings::values.has_broken_vulkan = true; - QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"), - tr("Vulkan initialization failed during boot.")); - + QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"), tr("Vulkan initialization failed during boot.")); #ifdef HAS_OPENGL - Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; + Settings::values.renderer_backend = Settings::RendererBackend::OpenGL_GLSL; #else Settings::values.renderer_backend = Settings::RendererBackend::Null; #endif @@ -667,13 +665,13 @@ MainWindow::MainWindow(bool has_broken_vulkan) } if (should_launch_setup) { - OnInitialSetup(); + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt); } else { if (!game_path.isEmpty()) { BootGame(game_path, ApplicationAppletParameters()); } else { if (should_launch_qlaunch) { - OnHomeMenu(); + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); } } } @@ -1620,19 +1618,32 @@ void MainWindow::ConnectMenuEvents() { connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &MainWindow::OnSaveConfig); // Tools - connect_menu(ui->action_Load_Album, &MainWindow::OnAlbum); - connect_menu(ui->action_Load_Cabinet_Nickname_Owner, - [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); }); - connect_menu(ui->action_Load_Cabinet_Eraser, - [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); }); - connect_menu(ui->action_Load_Cabinet_Restorer, - [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); }); - connect_menu(ui->action_Load_Cabinet_Formatter, - [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); }); - connect_menu(ui->action_Load_Mii_Edit, &MainWindow::OnMiiEdit); - connect_menu(ui->action_Open_Controller_Menu, &MainWindow::OnOpenControllerMenu); - connect_menu(ui->action_Load_Home_Menu, &MainWindow::OnHomeMenu); - connect_menu(ui->action_Open_Setup, &MainWindow::OnInitialSetup); + connect_menu(ui->action_Launch_PhotoViewer, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::PhotoViewer), std::nullopt); + }); + connect_menu(ui->action_Launch_MiiEdit, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::MiiEdit), std::nullopt); + }); + connect_menu(ui->action_Launch_Controller, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Controller), std::nullopt); + }); + connect_menu(ui->action_Launch_QLaunch, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); + }); + // Tools (cabinet) + connect_menu(ui->action_Launch_Cabinet_Nickname_Owner, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartNicknameAndOwnerSettings}); + }); + connect_menu(ui->action_Launch_Cabinet_Eraser, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartGameDataEraser}); + }); + connect_menu(ui->action_Launch_Cabinet_Restorer, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartRestorer}); + }); + connect_menu(ui->action_Launch_Cabinet_Formatter, [this]{ + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartFormatter}); + }); + connect_menu(ui->action_Desktop, &MainWindow::OnCreateHomeMenuDesktopShortcut); connect_menu(ui->action_Application_Menu, &MainWindow::OnCreateHomeMenuApplicationMenuShortcut); @@ -1673,14 +1684,16 @@ void MainWindow::UpdateMenuState() { ui->action_Pause, }; - const std::array applet_actions{ui->action_Load_Album, - ui->action_Load_Cabinet_Nickname_Owner, - ui->action_Load_Cabinet_Eraser, - ui->action_Load_Cabinet_Restorer, - ui->action_Load_Cabinet_Formatter, - ui->action_Load_Mii_Edit, - ui->action_Load_Home_Menu, - ui->action_Open_Controller_Menu}; + const std::array applet_actions{ + ui->action_Launch_PhotoViewer, + ui->action_Launch_Cabinet_Nickname_Owner, + ui->action_Launch_Cabinet_Eraser, + ui->action_Launch_Cabinet_Restorer, + ui->action_Launch_Cabinet_Formatter, + ui->action_Launch_MiiEdit, + ui->action_Launch_QLaunch, + ui->action_Launch_Controller + }; for (QAction* action : running_actions) { action->setEnabled(emulation_running); @@ -3625,7 +3638,7 @@ void MainWindow::OnToggleGraphicsAPI() { api = Settings::RendererBackend::Vulkan; } else { #ifdef HAS_OPENGL - api = Settings::RendererBackend::OpenGL; + api = Settings::RendererBackend::OpenGL_GLSL; #else api = Settings::RendererBackend::Null; #endif @@ -3905,157 +3918,62 @@ void MainWindow::OnGameListRefresh() SetFirmwareVersion(); } -void MainWindow::OnAlbum() { - constexpr u64 AlbumId = static_cast(Service::AM::AppletProgramId::PhotoViewer); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the Album applet.")); - return; - } - auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program); - if (!album_nca) { - QMessageBox::warning(this, tr("Album Applet"), - tr("Album applet is not available. Please reinstall firmware.")); - return; - } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::PhotoViewer); - - const auto filename = QString::fromStdString(album_nca->GetFullPath()); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, LibraryAppletParameters(AlbumId, Service::AM::AppletId::PhotoViewer)); -} - -void MainWindow::OnCabinet(Service::NFP::CabinetMode mode) { - constexpr u64 CabinetId = static_cast(Service::AM::AppletProgramId::Cabinet); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the Cabinet applet.")); - return; - } - - auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program); - if (!cabinet_nca) { - QMessageBox::warning(this, tr("Cabinet Applet"), - tr("Cabinet applet is not available. Please reinstall firmware.")); - return; - } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Cabinet); - QtCommon::system->GetFrontendAppletHolder().SetCabinetMode(mode); - - const auto filename = QString::fromStdString(cabinet_nca->GetFullPath()); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, LibraryAppletParameters(CabinetId, Service::AM::AppletId::Cabinet)); -} - -void MainWindow::OnMiiEdit() { - constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the Mii editor.")); - return; - } - - auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); - if (!mii_applet_nca) { - QMessageBox::warning(this, tr("Mii Edit Applet"), - tr("Mii editor is not available. Please reinstall firmware.")); - return; - } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::MiiEdit); - - const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath())); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, LibraryAppletParameters(MiiEditId, Service::AM::AppletId::MiiEdit)); -} - -void MainWindow::OnOpenControllerMenu() { - constexpr u64 ControllerAppletId = static_cast(Service::AM::AppletProgramId::Controller); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the Controller Menu.")); - return; - } - - auto controller_applet_nca = - bis_system->GetEntry(ControllerAppletId, FileSys::ContentRecordType::Program); - if (!controller_applet_nca) { - QMessageBox::warning(this, tr("Controller Applet"), - tr("Controller Menu is not available. Please reinstall firmware.")); - return; - } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Controller); - - const auto filename = QString::fromStdString((controller_applet_nca->GetFullPath())); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, - LibraryAppletParameters(ControllerAppletId, Service::AM::AppletId::Controller)); -} - -void MainWindow::OnHomeMenu() { +void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optional cabinet_mode) { + auto const program_id = Service::AM::AppletProgramId(raw_program_id); auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); - using namespace QtCommon::StringLookup; - switch (result) { case FirmwareManager::ErrorFirmwareMissing: - QMessageBox::warning(this, tr("No firmware available"), - Lookup(FwCheckErrorFirmwareMissing)); + QMessageBox::warning(this, tr("No firmware available"), Lookup(FwCheckErrorFirmwareMissing)); return; case FirmwareManager::ErrorFirmwareCorrupted: - QMessageBox::warning(this, tr("Firmware Corrupted"), - Lookup(FwCheckErrorFirmwareCorrupted)); + QMessageBox::warning(this, tr("Firmware Corrupted"), Lookup(FwCheckErrorFirmwareCorrupted)); return; default: break; } - - constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - - auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); - if (!qlaunch_applet_nca) { - QMessageBox::warning(this, tr("Home Menu Applet"), - tr("Home Menu is not available. Please reinstall firmware.")); - return; + if (auto applet_nca = bis_system->GetEntry(u64(program_id), FileSys::ContentRecordType::Program); applet_nca) { + if (auto const applet_id = [program_id] { + using namespace Service::AM; + switch (program_id) { + case AppletProgramId::OverlayDisplay: return AppletId::OverlayDisplay; + case AppletProgramId::QLaunch: return AppletId::QLaunch; + case AppletProgramId::Starter: return AppletId::Starter; + case AppletProgramId::Auth: return AppletId::Auth; + case AppletProgramId::Cabinet: return AppletId::Cabinet; + case AppletProgramId::Controller: return AppletId::Controller; + case AppletProgramId::DataErase: return AppletId::DataErase; + case AppletProgramId::Error: return AppletId::Error; + case AppletProgramId::NetConnect: return AppletId::NetConnect; + case AppletProgramId::ProfileSelect: return AppletId::ProfileSelect; + case AppletProgramId::SoftwareKeyboard: return AppletId::SoftwareKeyboard; + case AppletProgramId::MiiEdit: return AppletId::MiiEdit; + case AppletProgramId::Web: return AppletId::Web; + case AppletProgramId::Shop: return AppletId::Shop; + case AppletProgramId::PhotoViewer: return AppletId::PhotoViewer; + case AppletProgramId::Settings: return AppletId::Settings; + case AppletProgramId::OfflineWeb: return AppletId::OfflineWeb; + case AppletProgramId::LoginShare: return AppletId::LoginShare; + case AppletProgramId::WebAuth: return AppletId::WebAuth; + case AppletProgramId::MyPage: return AppletId::MyPage; + default: return AppletId::None; + } + }(); applet_id != Service::AM::AppletId::None) { + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(applet_id); + if (cabinet_mode) + QtCommon::system->GetFrontendAppletHolder().SetCabinetMode(*cabinet_mode); + // ? + auto const filename = QString::fromStdString((applet_nca->GetFullPath())); + UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); + BootGame(filename, LibraryAppletParameters(u64(program_id), applet_id)); + } else { + QMessageBox::warning(this, tr("Unknown applet"), tr("Applet doesn't map to a known value.")); + } + } else { + QMessageBox::warning(this, tr("Record not found"), tr("Applet not found. Please reinstall firmware.")); } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); - - const auto filename = QString::fromStdString((qlaunch_applet_nca->GetFullPath())); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, LibraryAppletParameters(QLaunchId, Service::AM::AppletId::QLaunch)); -} - -void MainWindow::OnInitialSetup() { - constexpr u64 Starter = static_cast(Service::AM::AppletProgramId::Starter); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use Starter.")); - return; - } - - auto qlaunch_nca = bis_system->GetEntry(Starter, FileSys::ContentRecordType::Program); - if (!qlaunch_nca) { - QMessageBox::warning(this, tr("Starter Applet"), - tr("Starter is not available. Please reinstall firmware.")); - return; - } - - QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Starter); - - const auto filename = QString::fromStdString((qlaunch_nca->GetFullPath())); - UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); - BootGame(filename, LibraryAppletParameters(Starter, Service::AM::AppletId::Starter)); } void MainWindow::OnCreateHomeMenuDesktopShortcut() { @@ -4270,13 +4188,7 @@ void MainWindow::UpdateAPIText() { const auto api = Settings::values.renderer_backend.GetValue(); const auto renderer_status_text = ConfigurationShared::renderer_backend_texts_map.find(api)->second; - renderer_status_button->setText( - api == Settings::RendererBackend::OpenGL - ? tr("%1 %2").arg(renderer_status_text.toUpper(), - ConfigurationShared::shader_backend_texts_map - .find(Settings::values.shader_backend.GetValue()) - ->second) - : renderer_status_text.toUpper()); + renderer_status_button->setText(renderer_status_text.toUpper()); } void MainWindow::UpdateFilterText() { diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index 754fc67c06..fe91ea18f1 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -101,6 +101,7 @@ class InputSubsystem; namespace Service::AM { struct FrontendAppletParameters; enum class AppletId : u32; +// this causes errors on debian -> enum class AppletProgramId : u64; } // namespace Service::AM namespace Service::AM::Frontend { @@ -399,12 +400,7 @@ private slots: void ResetWindowSize720(); void ResetWindowSize900(); void ResetWindowSize1080(); - void OnAlbum(); - void OnCabinet(Service::NFP::CabinetMode mode); - void OnMiiEdit(); - void OnOpenControllerMenu(); - void OnHomeMenu(); - void OnInitialSetup(); + void LaunchFirmwareApplet(u64 program_id, std::optional mode); void OnCreateHomeMenuDesktopShortcut(); void OnCreateHomeMenuApplicationMenuShortcut(); void OnCaptureScreenshot(); @@ -428,7 +424,6 @@ private: bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, u64* selected_title_id, u8* selected_content_record_type); ContentManager::InstallResult InstallNCA(const QString& filename); - void MigrateConfigFiles(); void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, std::string_view gpu_vendor = {}); void UpdateDockedButton(); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 975e72e00c..b292b4886b 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -339,7 +339,9 @@ int main(int argc, char** argv) { std::unique_ptr emu_window; switch (Settings::values.renderer_backend.GetValue()) { - case Settings::RendererBackend::OpenGL: + case Settings::RendererBackend::OpenGL_GLSL: + case Settings::RendererBackend::OpenGL_GLASM: + case Settings::RendererBackend::OpenGL_SPIRV: emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; case Settings::RendererBackend::Vulkan: diff --git a/tools/cpm/common.sh b/tools/cpm/common.sh index 436b2cedee..97a2fed003 100755 --- a/tools/cpm/common.sh +++ b/tools/cpm/common.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later ################################## @@ -9,6 +9,14 @@ # TODO: cache cpmfile defs +must_install() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { echo "-- $cmd must be installed" && exit 1; } + done +} + +must_install jq find mktemp tar 7z unzip sha512sum git patch curl xargs + # How many levels to go (3 is 2 subdirs max) MAXDEPTH=3 diff --git a/tools/cpm/migrate.sh b/tools/cpm/migrate.sh index adf72bd0bb..b24bbfbbbf 100755 --- a/tools/cpm/migrate.sh +++ b/tools/cpm/migrate.sh @@ -1,11 +1,10 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later SUBMODULES="$(git submodule status --recursive | cut -c2-)" - -[ -z "$SUBMODULES" ] && echo "No submodules defined!" && exit 0 +: "${SUBMODULES:?No submodules defined!}" tmp=$(mktemp) printf '{}' >"$tmp" @@ -22,7 +21,7 @@ for i in $SUBMODULES; do remote=$(git -C "$path" remote get-url origin) host=$(echo "$remote" | cut -d"/" -f3) - [ "$host" = github.com ] && host= + [ "$host" != github.com ] || host= repo=$(echo "$remote" | cut -d"/" -f4-5 | cut -d'.' -f1) diff --git a/tools/cpm/package.sh b/tools/cpm/package.sh index f64f40a3d2..474a2f351e 100755 --- a/tools/cpm/package.sh +++ b/tools/cpm/package.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later RETURN=0 @@ -31,48 +31,12 @@ export SCRIPTS while :; do case "$1" in - hash) + hash | update | fetch | add | rm | version | which | download) + cmd="$1" shift - "$SCRIPTS"/hash.sh "$@" + "$SCRIPTS/$cmd".sh "$@" break ;; - update) - shift - "$SCRIPTS"/update.sh "$@" - break - ;; - fetch) - shift - "$SCRIPTS"/fetch.sh "$@" - break - ;; - add) - shift - "$SCRIPTS"/add.sh "$@" - break - ;; - rm) - shift - "$SCRIPTS"/rm.sh "$@" - break - ;; - version) - shift - "$SCRIPTS"/version.sh "$@" - break - ;; - which) - shift - "$SCRIPTS"/which.sh "$@" - break - ;; - download) - shift - "$SCRIPTS"/download.sh "$@" - break - ;; - -h | --help) usage ;; - "") usage ;; *) usage ;; esac diff --git a/tools/cpm/package/add.sh b/tools/cpm/package/add.sh index 975e6fbae2..f8071ee1bd 100755 --- a/tools/cpm/package/add.sh +++ b/tools/cpm/package/add.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later RETURN=0 @@ -30,7 +30,7 @@ die() { } _cpmfile() { - [ -z "$1" ] && die "You must specify a valid cpmfile." + [ -n "$1" ] || die "You must specify a valid cpmfile." CPMFILE="$1" } @@ -70,7 +70,7 @@ done : "${CPMFILE:=$PWD/cpmfile.json}" -[ -z "$PKG" ] && die "You must specify a package name." +[ -n "$PKG" ] || die "You must specify a package name." export PKG export CPMFILE diff --git a/tools/cpm/package/download.sh b/tools/cpm/package/download.sh index c541426084..2a6125e74d 100755 --- a/tools/cpm/package/download.sh +++ b/tools/cpm/package/download.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later usage() { @@ -29,8 +29,8 @@ while :; do shift done -[ "$ALL" = 1 ] && packages="${LIBS:-$packages}" -[ -z "$packages" ] && usage +[ "$ALL" != 1 ] || packages="${LIBS:-$packages}" +[ -n "$packages" ] || usage for pkg in $packages; do PACKAGE="$pkg" diff --git a/tools/cpm/package/fetch.sh b/tools/cpm/package/fetch.sh index a1f3dc0cf1..a846f34b88 100755 --- a/tools/cpm/package/fetch.sh +++ b/tools/cpm/package/fetch.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later : "${CPM_SOURCE_CACHE:=$PWD/.cache/cpm}" @@ -73,11 +73,12 @@ download_package() { } ci_package() { - [ "$REPO" = null ] && echo "-- ! No repo defined" && return + [ "$REPO" != null ] || echo "-- ! No repo defined" && return echo "-- CI package $PACKAGE_NAME" - for platform in windows-amd64 windows-arm64 \ + for platform in \ + windows-amd64 windows-arm64 \ mingw-amd64 mingw-arm64 \ android-aarch64 android-x86_64 \ solaris-amd64 freebsd-amd64 openbsd-amd64 \ @@ -90,7 +91,6 @@ ci_package() { echo "-- * -- disabled" continue ;; - *) ;; esac FILENAME="${NAME}-${platform}-${VERSION}.${EXT}" @@ -102,7 +102,7 @@ ci_package() { [ -d "$OUTDIR" ] && continue HASH_ALGO=$(echo "$JSON" | jq -r ".hash_algo") - [ "$HASH_ALGO" = null ] && HASH_ALGO=sha512 + [ "$HASH_ALGO" != null ] || HASH_ALGO=sha512 HASH_SUFFIX="${HASH_ALGO}sum" HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}" @@ -137,8 +137,8 @@ while :; do shift done -[ "$ALL" = 1 ] && packages="${LIBS:-$packages}" -[ -z "$packages" ] && usage +[ "$ALL" != 1 ] || packages="${LIBS:-$packages}" +[ -n "$packages" ] || usage for PACKAGE in $packages; do export PACKAGE diff --git a/tools/cpm/package/hash.sh b/tools/cpm/package/hash.sh index 8a23894617..57bc1fee10 100755 --- a/tools/cpm/package/hash.sh +++ b/tools/cpm/package/hash.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later RETURN=0 @@ -52,9 +52,9 @@ while :; do shift done -[ "$ALL" = 1 ] && packages="${LIBS:-$packages}" +[ "$ALL" != 1 ] || packages="${LIBS:-$packages}" [ "$DRY" = 1 ] && UPDATE=false || UPDATE=true -[ -z "$packages" ] && usage +[ -n "$packages" ] || usage export UPDATE diff --git a/tools/cpm/package/rm.sh b/tools/cpm/package/rm.sh index 4af163492f..8b1ab94295 100755 --- a/tools/cpm/package/rm.sh +++ b/tools/cpm/package/rm.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later RETURN=0 @@ -16,7 +16,7 @@ EOF exit $RETURN } -[ $# -lt 1 ] && usage +[ $# -ge 1 ] || usage for pkg in "$@"; do JSON=$("$SCRIPTS"/which.sh "$pkg") || { diff --git a/tools/cpm/package/update.sh b/tools/cpm/package/update.sh index 362d4c14e0..be6a0fbbef 100755 --- a/tools/cpm/package/update.sh +++ b/tools/cpm/package/update.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later filter_out() { @@ -37,13 +37,13 @@ while :; do case "$char" in a) ALL=1 ;; - n) DRY=1 ;; + n) UPDATE=false ;; h) usage ;; *) die "Invalid option -$char" ;; esac done ;; - --dry-run) DRY=1 ;; + --dry-run) UPDATE=false ;; --all) ALL=1 ;; --help) usage ;; "$0") break ;; @@ -54,9 +54,9 @@ while :; do shift done -[ "$ALL" = 1 ] && packages="${LIBS:-$packages}" -[ "$DRY" = 1 ] && UPDATE=false || UPDATE=true -[ -z "$packages" ] && usage +[ "$ALL" != 1 ] || packages="${LIBS:-$packages}" +: "${UPDATE:=true}" +[ -n "$packages" ] || usage for pkg in $packages; do PACKAGE="$pkg" @@ -66,15 +66,15 @@ for pkg in $packages; do SKIP=$(value "skip_updates") - [ "$SKIP" = "true" ] && continue + [ "$SKIP" != "true" ] || continue - [ "$REPO" = null ] && continue - [ "$GIT_HOST" != "github.com" ] && continue # TODO + [ "$REPO" != null ] || continue + [ "$GIT_HOST" = "github.com" ] || continue # TODO - [ "$CI" = "true" ] && continue + [ "$CI" != "true" ] || continue # shellcheck disable=SC2153 - [ "$TAG" = null ] && continue + [ "$TAG" != null ] || continue echo "-- Package $PACKAGE" @@ -99,12 +99,15 @@ for pkg in $packages; do filter_out rc # Add package-specific overrides here, e.g. here for fmt: - [ "$PACKAGE" = fmt ] && filter_out v0.11 + [ "$PACKAGE" != fmt ] || filter_out v0.11 LATEST=$(echo "$TAGS" | jq -r '.[0].name') - [ "$LATEST" = "null" ] && echo "-- * Up-to-date" && continue - [ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ] && echo "-- * Up-to-date" && continue + if [ "$LATEST" = "null" ] || + { [ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ]; }; then + echo "-- * Up-to-date" + continue + fi if [ "$HAS_REPLACE" = "true" ]; then # this just extracts the tag prefix diff --git a/tools/cpm/package/util/fix-hash.sh b/tools/cpm/package/util/fix-hash.sh index 5d8dd0e163..07b9d266dd 100755 --- a/tools/cpm/package/util/fix-hash.sh +++ b/tools/cpm/package/util/fix-hash.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later : "${PACKAGE:=$1}" @@ -13,7 +13,7 @@ [ "$HASH_URL" = null ] || exit 0 [ "$HASH_SUFFIX" = null ] || exit 0 -[ "$HASH" = null ] && echo "-- * Package has no hash specified" && exit 0 +[ "$HASH" != null ] || { echo "-- * Package has no hash specified" && exit 0; } ACTUAL=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD") diff --git a/tools/cpm/package/util/interactive.sh b/tools/cpm/package/util/interactive.sh index 974228488a..99db77e20d 100755 --- a/tools/cpm/package/util/interactive.sh +++ b/tools/cpm/package/util/interactive.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # This reads a single-line input from the user and also gives them @@ -13,7 +13,7 @@ read_single() { while :; do printf -- "-- %s" "$1" - [ -n "$2" ] && printf " (? for help, %s)" "$3" + [ -z "$2" ] || printf " (? for help, %s)" "$3" printf ": " if ! IFS= read -r reply; then echo @@ -113,7 +113,7 @@ tag v1.3.0, then set this to 1.3.0 and set the tag to v%VERSION%." "Most commonly this will be something like v%VERSION% or release-%VERSION%, or just %VERSION%." TAGNAME="$reply" - [ -z "$TAGNAME" ] && TAGNAME="%VERSION%" + [ -n "$TAGNAME" ] || TAGNAME="%VERSION%" optional "Name of the release artifact to download, if applicable. -- %VERSION% is replaced by the numeric version and %TAG% is replaced by the tag name" \ @@ -160,9 +160,9 @@ fi jq_input='{repo: "'"$REPO"'"}' # common trivial fields -[ -n "$PACKAGE" ] && jq_input="$jq_input + {package: \"$PACKAGE\"}" -[ -n "$MIN_VERSION" ] && jq_input="$jq_input + {min_version: \"$MIN_VERSION\"}" -[ -n "$FIND_ARGS" ] && jq_input="$jq_input + {find_args: \"$FIND_ARGS\"}" +[ -z "$PACKAGE" ] || jq_input="$jq_input + {package: \"$PACKAGE\"}" +[ -z "$MIN_VERSION" ] || jq_input="$jq_input + {min_version: \"$MIN_VERSION\"}" +[ -z "$FIND_ARGS" ] || jq_input="$jq_input + {find_args: \"$FIND_ARGS\"}" if [ "$CI" = "true" ]; then jq_input="$jq_input + { @@ -177,7 +177,7 @@ if [ "$CI" = "true" ]; then jq_input="$jq_input + {disabled_platforms: $disabled_json}" fi else - [ -n "$MIN_VERSION" ] && jq_input="$jq_input + {version: \"$MIN_VERSION\"}" + [ -z "$MIN_VERSION" ] || jq_input="$jq_input + {version: \"$MIN_VERSION\"}" jq_input="$jq_input + {hash: \"\"}" # options @@ -194,8 +194,8 @@ else # versioning stuff if [ -n "$GIT_VERSION" ]; then jq_input="$jq_input + {git_version: \"$GIT_VERSION\"}" - [ -n "$TAGNAME" ] && jq_input="$jq_input + {tag: \"$TAGNAME\"}" - [ -n "$ARTIFACT" ] && jq_input="$jq_input + {artifact: \"$ARTIFACT\"}" + [ -z "$TAGNAME" ] || jq_input="$jq_input + {tag: \"$TAGNAME\"}" + [ -z "$ARTIFACT" ] || jq_input="$jq_input + {artifact: \"$ARTIFACT\"}" else jq_input="$jq_input + {sha: \"$SHA\"}" fi diff --git a/tools/cpm/package/vars.sh b/tools/cpm/package/vars.sh index 71bbb3e041..07f4883ddf 100755 --- a/tools/cpm/package/vars.sh +++ b/tools/cpm/package/vars.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # shellcheck disable=SC1091 @@ -9,12 +9,15 @@ value() { echo "$JSON" | jq -r ".$1" } -[ -z "$PACKAGE" ] && echo "Package was not specified" && exit 0 +[ -n "$PACKAGE" ] || { echo "Package was not specified" && exit 0; } # shellcheck disable=SC2153 JSON=$(echo "$PACKAGES" | jq -r ".\"$PACKAGE\" | select( . != null )") -[ -z "$JSON" ] && echo "!! No cpmfile definition for $PACKAGE" >&2 && exit 1 +if [ -z "$JSON" ]; then + echo "!! No cpmfile definition for $PACKAGE" >&2 + exit 1 +fi # unset stuff export PACKAGE_NAME="null" @@ -48,10 +51,10 @@ REPO=$(value "repo") CI=$(value "ci") PACKAGE_NAME=$(value "package") -[ "$PACKAGE_NAME" = null ] && PACKAGE_NAME="$PACKAGE" +[ "$PACKAGE_NAME" != null ] || PACKAGE_NAME="$PACKAGE" GIT_HOST=$(value "git_host") -[ "$GIT_HOST" = null ] && GIT_HOST=github.com +[ "$GIT_HOST" != null ] || GIT_HOST=github.com export PACKAGE_NAME export REPO @@ -66,12 +69,12 @@ VERSION=$(value "version") if [ "$CI" = "true" ]; then EXT=$(value "extension") - [ "$EXT" = null ] && EXT="tar.zst" + [ "$EXT" != null ] || EXT="tar.zst" NAME=$(value "name") DISABLED=$(echo "$JSON" | jq -j '.disabled_platforms') - [ "$NAME" = null ] && NAME="$PACKAGE_NAME" + [ "$NAME" != null ] || NAME="$PACKAGE_NAME" export EXT export NAME @@ -90,7 +93,7 @@ ARTIFACT=$(value "artifact") SHA=$(value "sha") GIT_VERSION=$(value "git_version") -[ "$GIT_VERSION" = null ] && GIT_VERSION="$VERSION" +[ "$GIT_VERSION" != null ] || GIT_VERSION="$VERSION" if [ "$GIT_VERSION" != null ]; then VERSION_REPLACE="$GIT_VERSION" @@ -98,8 +101,11 @@ else VERSION_REPLACE="$VERSION" fi -echo "$TAG" | grep -e "%VERSION%" >/dev/null && - HAS_REPLACE=true || HAS_REPLACE=false +if echo "$TAG" | grep -e "%VERSION%" >/dev/null; then + HAS_REPLACE=true +else + HAS_REPLACE=false +fi ORIGINAL_TAG="$TAG" @@ -145,7 +151,7 @@ export KEY ################ HASH_ALGO=$(value "hash_algo") -[ "$HASH_ALGO" = null ] && HASH_ALGO=sha512 +[ "$HASH_ALGO" != null ] || HASH_ALGO=sha512 HASH=$(value "hash") diff --git a/tools/cpm/package/version.sh b/tools/cpm/package/version.sh index b92098f532..fef78124a0 100755 --- a/tools/cpm/package/version.sh +++ b/tools/cpm/package/version.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # shellcheck disable=SC1091 @@ -20,14 +20,14 @@ EOF PACKAGE="$1" NEW_VERSION="$2" -[ -z "$PACKAGE" ] && usage -[ -z "$NEW_VERSION" ] && usage +[ -n "$PACKAGE" ] || usage +[ -n "$NEW_VERSION" ] || usage export PACKAGE . "$SCRIPTS"/vars.sh -[ "$REPO" = null ] && exit 0 +[ "$REPO" != null ] || exit 0 if [ "$HAS_REPLACE" = "true" ]; then # this just extracts the tag prefix @@ -54,7 +54,7 @@ fi echo "-- * -- Updating $PACKAGE to version $NEW_VERSION" "$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON" -[ "$CI" = "true" ] && exit 0 +[ "$CI" != "true" ] || exit 0 echo "-- * -- Fixing hash" . "$ROOTDIR"/common.sh UPDATE=true QUIET=true "$SCRIPTS"/util/fix-hash.sh diff --git a/tools/cpm/package/which.sh b/tools/cpm/package/which.sh index de3ca41c02..24c80760e3 100755 --- a/tools/cpm/package/which.sh +++ b/tools/cpm/package/which.sh @@ -1,12 +1,12 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # check which file a package is in JSON=$(echo "$CPMFILES" | xargs grep -l "\"$1\"") -[ -z "$JSON" ] && echo "!! No cpmfile definition for $1" >&2 && exit 1 +[ -n "$JSON" ] || { echo "!! No cpmfile definition for $1" >&2 && exit 1; } echo "$JSON" diff --git a/tools/cpm/update.sh b/tools/cpm/update.sh index 81bb84c80a..15fd937242 100755 --- a/tools/cpm/update.sh +++ b/tools/cpm/update.sh @@ -1,16 +1,19 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # updates CPMUtil, its docs, and related tools from the latest release -if command -v zstd >/dev/null; then +if command -v zstd >/dev/null 2>&1; then EXT=tar.zst else EXT=tar.gz fi -wget "https://git.crueter.xyz/CMake/CPMUtil/releases/download/continuous/CPMUtil.$EXT" -tar xf "CPMUtil.$EXT" -rm "CPMUtil.$EXT" +file=CPMUtil.$EXT +url="https://git.crueter.xyz/CMake/CPMUtil/releases/download/continuous/$file" + +curl -L "$url" -o "$file" +tar xf "$file" +rm "$file" diff --git a/tools/cpmutil.sh b/tools/cpmutil.sh index 52afba2623..cec758a726 100755 --- a/tools/cpmutil.sh +++ b/tools/cpmutil.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later # shellcheck disable=SC1091 @@ -49,26 +49,18 @@ export ROOTDIR while :; do case "$1" in - -h | --help) usage ;; ls) echo "$CPMFILES" | tr ' ' '\n' break ;; - format) - "$SCRIPTS"/format.sh - break - ;; - update) - "$SCRIPTS"/update.sh - break - ;; - migrate) - "$SCRIPTS"/migrate.sh + format | update | migrate) + "$SCRIPTS/$1".sh break ;; package) + cmd="$1" shift - "$SCRIPTS"/package.sh "$@" + "$SCRIPTS/$cmd".sh "$@" break ;; *) usage ;;