diff options
145 files changed, 2483 insertions, 1157 deletions
diff --git a/.gitignore b/.gitignore index a5f7248c7..fbadb208b 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -26,6 +26,8 @@ CMakeSettings.json | |||
| 26 | # OSX global filetypes | 26 | # OSX global filetypes |
| 27 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) | 27 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) |
| 28 | .DS_Store | 28 | .DS_Store |
| 29 | .DS_Store? | ||
| 30 | ._* | ||
| 29 | .AppleDouble | 31 | .AppleDouble |
| 30 | .LSOverride | 32 | .LSOverride |
| 31 | .Spotlight-V100 | 33 | .Spotlight-V100 |
diff --git a/.gitmodules b/.gitmodules index 95eae8109..89f2ad924 100644 --- a/.gitmodules +++ b/.gitmodules | |||
| @@ -52,3 +52,6 @@ | |||
| 52 | [submodule "libadrenotools"] | 52 | [submodule "libadrenotools"] |
| 53 | path = externals/libadrenotools | 53 | path = externals/libadrenotools |
| 54 | url = https://github.com/bylaws/libadrenotools | 54 | url = https://github.com/bylaws/libadrenotools |
| 55 | [submodule "tzdb_to_nx"] | ||
| 56 | path = externals/nx_tzdb/tzdb_to_nx | ||
| 57 | url = https://github.com/lat9nq/tzdb_to_nx.git | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d03bbf94..6d3146c9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -59,6 +59,8 @@ option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON) | |||
| 59 | 59 | ||
| 60 | option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) | 60 | option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) |
| 61 | 61 | ||
| 62 | option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF) | ||
| 63 | |||
| 62 | CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) | 64 | CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) |
| 63 | 65 | ||
| 64 | # On Android, fetch and compile libcxx before doing anything else | 66 | # On Android, fetch and compile libcxx before doing anything else |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e48137080..7cce27d51 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -63,8 +63,9 @@ if (YUZU_USE_EXTERNAL_SDL2) | |||
| 63 | # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers | 63 | # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers |
| 64 | # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) | 64 | # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) |
| 65 | # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) | 65 | # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) |
| 66 | # CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809) | ||
| 66 | set(SDL_UNUSED_SUBSYSTEMS | 67 | set(SDL_UNUSED_SUBSYSTEMS |
| 67 | CPUinfo File Filesystem | 68 | File Filesystem |
| 68 | Locale Power Render) | 69 | Locale Power Render) |
| 69 | foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) | 70 | foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) |
| 70 | string(TOUPPER ${_SUB} _OPT) | 71 | string(TOUPPER ${_SUB} _OPT) |
diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt index 2f625c108..593786250 100644 --- a/externals/nx_tzdb/CMakeLists.txt +++ b/externals/nx_tzdb/CMakeLists.txt | |||
| @@ -1,24 +1,60 @@ | |||
| 1 | # SPDX-FileCopyrightText: 2023 yuzu Emulator Project | 1 | # SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
| 2 | # SPDX-License-Identifier: GPL-2.0-or-later | 2 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | set(NX_TZDB_VERSION "220816") | 4 | set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") |
| 5 | set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip") | 5 | |
| 6 | add_library(nx_tzdb INTERFACE) | ||
| 7 | |||
| 8 | find_program(GIT git) | ||
| 9 | find_program(GNU_MAKE make) | ||
| 10 | find_program(DATE_PROG date) | ||
| 6 | 11 | ||
| 12 | set(CAN_BUILD_NX_TZDB true) | ||
| 13 | |||
| 14 | if (NOT GIT) | ||
| 15 | set(CAN_BUILD_NX_TZDB false) | ||
| 16 | endif() | ||
| 17 | if (NOT GNU_MAKE) | ||
| 18 | set(CAN_BUILD_NX_TZDB false) | ||
| 19 | endif() | ||
| 20 | if (NOT DATE_PROG) | ||
| 21 | set(CAN_BUILD_NX_TZDB false) | ||
| 22 | endif() | ||
| 23 | if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID) | ||
| 24 | # tzdb_to_nx currently requires a posix-compliant host | ||
| 25 | # MinGW and Android are handled here due to the executable format being different from the host system | ||
| 26 | # TODO (lat9nq): cross-compiling support | ||
| 27 | set(CAN_BUILD_NX_TZDB false) | ||
| 28 | endif() | ||
| 29 | |||
| 30 | set(NX_TZDB_VERSION "220816") | ||
| 7 | set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") | 31 | set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") |
| 8 | set(NX_TZDB_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") | ||
| 9 | 32 | ||
| 10 | set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") | 33 | set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") |
| 34 | |||
| 35 | if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE}) | ||
| 36 | set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip") | ||
| 37 | |||
| 38 | message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...") | ||
| 39 | file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE} | ||
| 40 | STATUS NX_TZDB_DOWNLOAD_STATUS) | ||
| 41 | list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE) | ||
| 42 | if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0) | ||
| 43 | message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})") | ||
| 44 | endif() | ||
| 11 | 45 | ||
| 12 | if (NOT EXISTS ${NX_TZDB_ARCHIVE}) | ||
| 13 | file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}) | ||
| 14 | file(ARCHIVE_EXTRACT | 46 | file(ARCHIVE_EXTRACT |
| 15 | INPUT | 47 | INPUT |
| 16 | ${NX_TZDB_ARCHIVE} | 48 | ${NX_TZDB_ARCHIVE} |
| 17 | DESTINATION | 49 | DESTINATION |
| 18 | ${NX_TZDB_DIR}) | 50 | ${NX_TZDB_ROMFS_DIR}) |
| 51 | elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA) | ||
| 52 | add_subdirectory(tzdb_to_nx) | ||
| 53 | add_dependencies(nx_tzdb x80e) | ||
| 54 | |||
| 55 | set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}") | ||
| 19 | endif() | 56 | endif() |
| 20 | 57 | ||
| 21 | add_library(nx_tzdb INTERFACE) | ||
| 22 | target_include_directories(nx_tzdb | 58 | target_include_directories(nx_tzdb |
| 23 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include | 59 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 24 | INTERFACE ${NX_TZDB_INCLUDE_DIR}) | 60 | INTERFACE ${NX_TZDB_INCLUDE_DIR}) |
| @@ -41,25 +77,25 @@ function(CreateHeader ZONE_PATH HEADER_NAME) | |||
| 41 | target_sources(nx_tzdb PRIVATE ${HEADER_PATH}) | 77 | target_sources(nx_tzdb PRIVATE ${HEADER_PATH}) |
| 42 | endfunction() | 78 | endfunction() |
| 43 | 79 | ||
| 44 | CreateHeader(${NX_TZDB_DIR} base) | 80 | CreateHeader(${NX_TZDB_ROMFS_DIR} base) |
| 45 | CreateHeader(${NX_TZDB_DIR}/zoneinfo zoneinfo) | 81 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo) |
| 46 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Africa africa) | 82 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa) |
| 47 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/America america) | 83 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america) |
| 48 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Argentina america_argentina) | 84 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina) |
| 49 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Indiana america_indiana) | 85 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana) |
| 50 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Kentucky america_kentucky) | 86 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky) |
| 51 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/North_Dakota america_north_dakota) | 87 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota) |
| 52 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Antartica antartica) | 88 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica) |
| 53 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Arctic arctic) | 89 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic) |
| 54 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Asia asia) | 90 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia) |
| 55 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Atlantic atlantic) | 91 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic) |
| 56 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Australia australia) | 92 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia) |
| 57 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Brazil brazil) | 93 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil) |
| 58 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Canada canada) | 94 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada) |
| 59 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Chile chile) | 95 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile) |
| 60 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Etc etc) | 96 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc) |
| 61 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Europe europe) | 97 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe) |
| 62 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Indian indian) | 98 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian) |
| 63 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Mexico mexico) | 99 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico) |
| 64 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/Pacific pacific) | 100 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific) |
| 65 | CreateHeader(${NX_TZDB_DIR}/zoneinfo/US us) | 101 | CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/US us) |
diff --git a/externals/nx_tzdb/NxTzdbCreateHeader.cmake b/externals/nx_tzdb/NxTzdbCreateHeader.cmake index 69166aa5b..8c29e1167 100644 --- a/externals/nx_tzdb/NxTzdbCreateHeader.cmake +++ b/externals/nx_tzdb/NxTzdbCreateHeader.cmake | |||
| @@ -15,7 +15,7 @@ set(DIRECTORY_NAME ${HEADER_NAME}) | |||
| 15 | 15 | ||
| 16 | set(FILE_DATA "") | 16 | set(FILE_DATA "") |
| 17 | foreach(ZONE_FILE ${FILE_LIST}) | 17 | foreach(ZONE_FILE ${FILE_LIST}) |
| 18 | if ("${ZONE_FILE}" STREQUAL "\n") | 18 | if (ZONE_FILE STREQUAL "\n") |
| 19 | continue() | 19 | continue() |
| 20 | endif() | 20 | endif() |
| 21 | 21 | ||
| @@ -26,13 +26,13 @@ foreach(ZONE_FILE ${FILE_LIST}) | |||
| 26 | foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) | 26 | foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) |
| 27 | math(EXPR BREAK_LINE "(${I} + 2) % 38") | 27 | math(EXPR BREAK_LINE "(${I} + 2) % 38") |
| 28 | 28 | ||
| 29 | string(SUBSTRING "${ZONE_DATA}" "${I}" "2" HEX_DATA) | 29 | string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA) |
| 30 | if ("${HEX_DATA}" STREQUAL "") | 30 | if (NOT HEX_DATA) |
| 31 | break() | 31 | break() |
| 32 | endif() | 32 | endif() |
| 33 | 33 | ||
| 34 | string(APPEND FILE_DATA "0x${HEX_DATA},") | 34 | string(APPEND FILE_DATA "0x${HEX_DATA},") |
| 35 | if ("${BREAK_LINE}" STREQUAL "0") | 35 | if (BREAK_LINE EQUAL 0) |
| 36 | string(APPEND FILE_DATA "\n") | 36 | string(APPEND FILE_DATA "\n") |
| 37 | else() | 37 | else() |
| 38 | string(APPEND FILE_DATA " ") | 38 | string(APPEND FILE_DATA " ") |
diff --git a/externals/nx_tzdb/include/nx_tzdb.h b/externals/nx_tzdb/include/nx_tzdb.h index d7b1e4304..1f7c6069a 100644 --- a/externals/nx_tzdb/include/nx_tzdb.h +++ b/externals/nx_tzdb/include/nx_tzdb.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "nx_tzdb/america_indiana.h" | 9 | #include "nx_tzdb/america_indiana.h" |
| 10 | #include "nx_tzdb/america_kentucky.h" | 10 | #include "nx_tzdb/america_kentucky.h" |
| 11 | #include "nx_tzdb/america_north_dakota.h" | 11 | #include "nx_tzdb/america_north_dakota.h" |
| 12 | #include "nx_tzdb/antartica.h" | 12 | #include "nx_tzdb/antarctica.h" |
| 13 | #include "nx_tzdb/arctic.h" | 13 | #include "nx_tzdb/arctic.h" |
| 14 | #include "nx_tzdb/asia.h" | 14 | #include "nx_tzdb/asia.h" |
| 15 | #include "nx_tzdb/atlantic.h" | 15 | #include "nx_tzdb/atlantic.h" |
diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx new file mode 160000 | |||
| Subproject 8c272f21d19c6e821345fd055f41b9640f9189d | |||
diff --git a/externals/vcpkg b/externals/vcpkg | |||
| Subproject a487471068f4cb1cbb4eeb340763cdcc0a75fd6 | Subproject cbf56573a987527b39272e88cbdd11389b78c6e | ||
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index fe613d339..bab4f4d0f 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -2,13 +2,17 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | import android.annotation.SuppressLint | 4 | import android.annotation.SuppressLint |
| 5 | import kotlin.collections.setOf | ||
| 5 | import org.jetbrains.kotlin.konan.properties.Properties | 6 | import org.jetbrains.kotlin.konan.properties.Properties |
| 7 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType | ||
| 6 | 8 | ||
| 7 | plugins { | 9 | plugins { |
| 8 | id("com.android.application") | 10 | id("com.android.application") |
| 9 | id("org.jetbrains.kotlin.android") | 11 | id("org.jetbrains.kotlin.android") |
| 10 | id("kotlin-parcelize") | 12 | id("kotlin-parcelize") |
| 11 | kotlin("plugin.serialization") version "1.8.21" | 13 | kotlin("plugin.serialization") version "1.8.21" |
| 14 | id("androidx.navigation.safeargs.kotlin") | ||
| 15 | id("org.jlleitschuh.gradle.ktlint") version "11.4.0" | ||
| 12 | } | 16 | } |
| 13 | 17 | ||
| 14 | /** | 18 | /** |
| @@ -43,16 +47,6 @@ android { | |||
| 43 | jniLibs.useLegacyPackaging = true | 47 | jniLibs.useLegacyPackaging = true |
| 44 | } | 48 | } |
| 45 | 49 | ||
| 46 | lint { | ||
| 47 | // This is important as it will run lint but not abort on error | ||
| 48 | // Lint has some overly obnoxious "errors" that should really be warnings | ||
| 49 | abortOnError = false | ||
| 50 | |||
| 51 | //Uncomment disable lines for test builds... | ||
| 52 | //disable 'MissingTranslation'bin | ||
| 53 | //disable 'ExtraTranslation' | ||
| 54 | } | ||
| 55 | |||
| 56 | defaultConfig { | 50 | defaultConfig { |
| 57 | // TODO If this is ever modified, change application_id in strings.xml | 51 | // TODO If this is ever modified, change application_id in strings.xml |
| 58 | applicationId = "org.yuzu.yuzu_emu" | 52 | applicationId = "org.yuzu.yuzu_emu" |
| @@ -166,6 +160,24 @@ android { | |||
| 166 | } | 160 | } |
| 167 | } | 161 | } |
| 168 | 162 | ||
| 163 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") | ||
| 164 | |||
| 165 | ktlint { | ||
| 166 | version.set("0.47.1") | ||
| 167 | android.set(true) | ||
| 168 | ignoreFailures.set(false) | ||
| 169 | disabledRules.set( | ||
| 170 | setOf( | ||
| 171 | "no-wildcard-imports", | ||
| 172 | "package-name", | ||
| 173 | "import-ordering" | ||
| 174 | ) | ||
| 175 | ) | ||
| 176 | reporters { | ||
| 177 | reporter(ReporterType.CHECKSTYLE) | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 169 | dependencies { | 181 | dependencies { |
| 170 | implementation("androidx.core:core-ktx:1.10.1") | 182 | implementation("androidx.core:core-ktx:1.10.1") |
| 171 | implementation("androidx.appcompat:appcompat:1.6.1") | 183 | implementation("androidx.appcompat:appcompat:1.6.1") |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 55f62b4b9..e31ad69e2 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -53,8 +53,9 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 53 | <activity | 53 | <activity |
| 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
| 55 | android:theme="@style/Theme.Yuzu.Main" | 55 | android:theme="@style/Theme.Yuzu.Main" |
| 56 | android:launchMode="singleTop" | ||
| 57 | android:screenOrientation="userLandscape" | 56 | android:screenOrientation="userLandscape" |
| 57 | android:supportsPictureInPicture="true" | ||
| 58 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" | ||
| 58 | android:exported="true"> | 59 | android:exported="true"> |
| 59 | 60 | ||
| 60 | <intent-filter> | 61 | <intent-filter> |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 4be9ade14..f860cdd4b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -14,16 +14,18 @@ import android.widget.TextView | |||
| 14 | import androidx.annotation.Keep | 14 | import androidx.annotation.Keep |
| 15 | import androidx.fragment.app.DialogFragment | 15 | import androidx.fragment.app.DialogFragment |
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 17 | import java.lang.ref.WeakReference | ||
| 17 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext | 18 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext |
| 18 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 19 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 19 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath | 20 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath |
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.exists | ||
| 20 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize |
| 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory | ||
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri |
| 22 | import org.yuzu.yuzu_emu.utils.Log.error | 25 | import org.yuzu.yuzu_emu.utils.Log.error |
| 23 | import org.yuzu.yuzu_emu.utils.Log.verbose | 26 | import org.yuzu.yuzu_emu.utils.Log.verbose |
| 24 | import org.yuzu.yuzu_emu.utils.Log.warning | 27 | import org.yuzu.yuzu_emu.utils.Log.warning |
| 25 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 28 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 26 | import java.lang.ref.WeakReference | ||
| 27 | 29 | ||
| 28 | /** | 30 | /** |
| 29 | * Class which contains methods that interact | 31 | * Class which contains methods that interact |
| @@ -74,7 +76,9 @@ object NativeLibrary { | |||
| 74 | fun openContentUri(path: String?, openmode: String?): Int { | 76 | fun openContentUri(path: String?, openmode: String?): Int { |
| 75 | return if (isNativePath(path!!)) { | 77 | return if (isNativePath(path!!)) { |
| 76 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) | 78 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) |
| 77 | } else openContentUri(appContext, path, openmode) | 79 | } else { |
| 80 | openContentUri(appContext, path, openmode) | ||
| 81 | } | ||
| 78 | } | 82 | } |
| 79 | 83 | ||
| 80 | @Keep | 84 | @Keep |
| @@ -82,7 +86,29 @@ object NativeLibrary { | |||
| 82 | fun getSize(path: String?): Long { | 86 | fun getSize(path: String?): Long { |
| 83 | return if (isNativePath(path!!)) { | 87 | return if (isNativePath(path!!)) { |
| 84 | YuzuApplication.documentsTree!!.getFileSize(path) | 88 | YuzuApplication.documentsTree!!.getFileSize(path) |
| 85 | } else getFileSize(appContext, path) | 89 | } else { |
| 90 | getFileSize(appContext, path) | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | @Keep | ||
| 95 | @JvmStatic | ||
| 96 | fun exists(path: String?): Boolean { | ||
| 97 | return if (isNativePath(path!!)) { | ||
| 98 | YuzuApplication.documentsTree!!.exists(path) | ||
| 99 | } else { | ||
| 100 | exists(appContext, path) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | @Keep | ||
| 105 | @JvmStatic | ||
| 106 | fun isDirectory(path: String?): Boolean { | ||
| 107 | return if (isNativePath(path!!)) { | ||
| 108 | YuzuApplication.documentsTree!!.isDirectory(path) | ||
| 109 | } else { | ||
| 110 | isDirectory(appContext, path) | ||
| 111 | } | ||
| 86 | } | 112 | } |
| 87 | 113 | ||
| 88 | /** | 114 | /** |
| @@ -283,6 +309,11 @@ object NativeLibrary { | |||
| 283 | external fun isRunning(): Boolean | 309 | external fun isRunning(): Boolean |
| 284 | 310 | ||
| 285 | /** | 311 | /** |
| 312 | * Returns true if emulation is paused. | ||
| 313 | */ | ||
| 314 | external fun isPaused(): Boolean | ||
| 315 | |||
| 316 | /** | ||
| 286 | * Returns the performance stats for the current game | 317 | * Returns the performance stats for the current game |
| 287 | */ | 318 | */ |
| 288 | external fun getPerfStats(): DoubleArray | 319 | external fun getPerfStats(): DoubleArray |
| @@ -431,7 +462,9 @@ object NativeLibrary { | |||
| 431 | Html.FROM_HTML_MODE_LEGACY | 462 | Html.FROM_HTML_MODE_LEGACY |
| 432 | ) | 463 | ) |
| 433 | ) | 464 | ) |
| 434 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } | 465 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> |
| 466 | emulationActivity.finish() | ||
| 467 | } | ||
| 435 | .setOnDismissListener { emulationActivity.finish() } | 468 | .setOnDismissListener { emulationActivity.finish() } |
| 436 | emulationActivity.runOnUiThread { | 469 | emulationActivity.runOnUiThread { |
| 437 | val alert = builder.create() | 470 | val alert = builder.create() |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 4c947b786..04ab6a220 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt | |||
| @@ -7,12 +7,12 @@ import android.app.Application | |||
| 7 | import android.app.NotificationChannel | 7 | import android.app.NotificationChannel |
| 8 | import android.app.NotificationManager | 8 | import android.app.NotificationManager |
| 9 | import android.content.Context | 9 | import android.content.Context |
| 10 | import java.io.File | ||
| 10 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 11 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 11 | import org.yuzu.yuzu_emu.utils.DocumentsTree | 12 | import org.yuzu.yuzu_emu.utils.DocumentsTree |
| 12 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 13 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 13 | import java.io.File | ||
| 14 | 14 | ||
| 15 | fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir | 15 | fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir |
| 16 | 16 | ||
| 17 | class YuzuApplication : Application() { | 17 | class YuzuApplication : Application() { |
| 18 | private fun createNotificationChannels() { | 18 | private fun createNotificationChannels() { |
| @@ -21,7 +21,9 @@ class YuzuApplication : Application() { | |||
| 21 | getString(R.string.emulation_notification_channel_name), | 21 | getString(R.string.emulation_notification_channel_name), |
| 22 | NotificationManager.IMPORTANCE_LOW | 22 | NotificationManager.IMPORTANCE_LOW |
| 23 | ) | 23 | ) |
| 24 | emulationChannel.description = getString(R.string.emulation_notification_channel_description) | 24 | emulationChannel.description = getString( |
| 25 | R.string.emulation_notification_channel_description | ||
| 26 | ) | ||
| 25 | emulationChannel.setSound(null, null) | 27 | emulationChannel.setSound(null, null) |
| 26 | emulationChannel.vibrationPattern = null | 28 | emulationChannel.vibrationPattern = null |
| 27 | 29 | ||
| @@ -48,7 +50,7 @@ class YuzuApplication : Application() { | |||
| 48 | GpuDriverHelper.initializeDriverParameters(applicationContext) | 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) |
| 49 | NativeLibrary.logDeviceInfo() | 51 | NativeLibrary.logDeviceInfo() |
| 50 | 52 | ||
| 51 | createNotificationChannels(); | 53 | createNotificationChannels() |
| 52 | } | 54 | } |
| 53 | 55 | ||
| 54 | companion object { | 56 | companion object { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 20a0394f5..f0a6753a9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
| @@ -4,14 +4,23 @@ | |||
| 4 | package org.yuzu.yuzu_emu.activities | 4 | package org.yuzu.yuzu_emu.activities |
| 5 | 5 | ||
| 6 | import android.app.Activity | 6 | import android.app.Activity |
| 7 | import android.app.PendingIntent | ||
| 8 | import android.app.PictureInPictureParams | ||
| 9 | import android.app.RemoteAction | ||
| 10 | import android.content.BroadcastReceiver | ||
| 7 | import android.content.Context | 11 | import android.content.Context |
| 8 | import android.content.Intent | 12 | import android.content.Intent |
| 13 | import android.content.IntentFilter | ||
| 14 | import android.content.res.Configuration | ||
| 9 | import android.graphics.Rect | 15 | import android.graphics.Rect |
| 16 | import android.graphics.drawable.Icon | ||
| 10 | import android.hardware.Sensor | 17 | import android.hardware.Sensor |
| 11 | import android.hardware.SensorEvent | 18 | import android.hardware.SensorEvent |
| 12 | import android.hardware.SensorEventListener | 19 | import android.hardware.SensorEventListener |
| 13 | import android.hardware.SensorManager | 20 | import android.hardware.SensorManager |
| 21 | import android.os.Build | ||
| 14 | import android.os.Bundle | 22 | import android.os.Bundle |
| 23 | import android.util.Rational | ||
| 15 | import android.view.InputDevice | 24 | import android.view.InputDevice |
| 16 | import android.view.KeyEvent | 25 | import android.view.KeyEvent |
| 17 | import android.view.MotionEvent | 26 | import android.view.MotionEvent |
| @@ -23,30 +32,27 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 23 | import androidx.core.view.WindowCompat | 32 | import androidx.core.view.WindowCompat |
| 24 | import androidx.core.view.WindowInsetsCompat | 33 | import androidx.core.view.WindowInsetsCompat |
| 25 | import androidx.core.view.WindowInsetsControllerCompat | 34 | import androidx.core.view.WindowInsetsControllerCompat |
| 26 | import androidx.lifecycle.Lifecycle | 35 | import androidx.navigation.fragment.NavHostFragment |
| 27 | import androidx.lifecycle.lifecycleScope | 36 | import kotlin.math.roundToInt |
| 28 | import androidx.lifecycle.repeatOnLifecycle | ||
| 29 | import androidx.window.layout.WindowInfoTracker | ||
| 30 | import kotlinx.coroutines.Dispatchers | ||
| 31 | import kotlinx.coroutines.launch | ||
| 32 | import org.yuzu.yuzu_emu.NativeLibrary | 37 | import org.yuzu.yuzu_emu.NativeLibrary |
| 33 | import org.yuzu.yuzu_emu.R | 38 | import org.yuzu.yuzu_emu.R |
| 39 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | ||
| 40 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 41 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 34 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | 42 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel |
| 35 | import org.yuzu.yuzu_emu.fragments.EmulationFragment | ||
| 36 | import org.yuzu.yuzu_emu.model.Game | 43 | import org.yuzu.yuzu_emu.model.Game |
| 37 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 44 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| 38 | import org.yuzu.yuzu_emu.utils.ForegroundService | 45 | import org.yuzu.yuzu_emu.utils.ForegroundService |
| 39 | import org.yuzu.yuzu_emu.utils.InputHandler | 46 | import org.yuzu.yuzu_emu.utils.InputHandler |
| 40 | import org.yuzu.yuzu_emu.utils.NfcReader | 47 | import org.yuzu.yuzu_emu.utils.NfcReader |
| 41 | import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable | ||
| 42 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 48 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
| 43 | import kotlin.math.roundToInt | ||
| 44 | 49 | ||
| 45 | class EmulationActivity : AppCompatActivity(), SensorEventListener { | 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { |
| 51 | private lateinit var binding: ActivityEmulationBinding | ||
| 52 | |||
| 46 | private var controllerMappingHelper: ControllerMappingHelper? = null | 53 | private var controllerMappingHelper: ControllerMappingHelper? = null |
| 47 | 54 | ||
| 48 | var isActivityRecreated = false | 55 | var isActivityRecreated = false |
| 49 | private var emulationFragment: EmulationFragment? = null | ||
| 50 | private lateinit var nfcReader: NfcReader | 56 | private lateinit var nfcReader: NfcReader |
| 51 | private lateinit var inputHandler: InputHandler | 57 | private lateinit var inputHandler: InputHandler |
| 52 | 58 | ||
| @@ -55,7 +61,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 55 | private var motionTimestamp: Long = 0 | 61 | private var motionTimestamp: Long = 0 |
| 56 | private var flipMotionOrientation: Boolean = false | 62 | private var flipMotionOrientation: Boolean = false |
| 57 | 63 | ||
| 58 | private lateinit var game: Game | 64 | private val actionPause = "ACTION_EMULATOR_PAUSE" |
| 65 | private val actionPlay = "ACTION_EMULATOR_PLAY" | ||
| 59 | 66 | ||
| 60 | private val settingsViewModel: SettingsViewModel by viewModels() | 67 | private val settingsViewModel: SettingsViewModel by viewModels() |
| 61 | 68 | ||
| @@ -70,47 +77,31 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 70 | settingsViewModel.settings.loadSettings() | 77 | settingsViewModel.settings.loadSettings() |
| 71 | 78 | ||
| 72 | super.onCreate(savedInstanceState) | 79 | super.onCreate(savedInstanceState) |
| 73 | if (savedInstanceState == null) { | 80 | |
| 74 | // Get params we were passed | 81 | binding = ActivityEmulationBinding.inflate(layoutInflater) |
| 75 | game = intent.parcelable(EXTRA_SELECTED_GAME)!! | 82 | setContentView(binding.root) |
| 76 | isActivityRecreated = false | 83 | |
| 77 | } else { | 84 | val navHostFragment = |
| 78 | isActivityRecreated = true | 85 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 79 | restoreState(savedInstanceState) | 86 | val navController = navHostFragment.navController |
| 80 | } | 87 | navController |
| 88 | .setGraph(R.navigation.emulation_navigation, intent.extras) | ||
| 89 | |||
| 90 | isActivityRecreated = savedInstanceState != null | ||
| 91 | |||
| 81 | controllerMappingHelper = ControllerMappingHelper() | 92 | controllerMappingHelper = ControllerMappingHelper() |
| 82 | 93 | ||
| 83 | // Set these options now so that the SurfaceView the game renders into is the right size. | 94 | // Set these options now so that the SurfaceView the game renders into is the right size. |
| 84 | enableFullscreenImmersive() | 95 | enableFullscreenImmersive() |
| 85 | 96 | ||
| 86 | setContentView(R.layout.activity_emulation) | ||
| 87 | window.decorView.setBackgroundColor(getColor(android.R.color.black)) | 97 | window.decorView.setBackgroundColor(getColor(android.R.color.black)) |
| 88 | 98 | ||
| 89 | // Find or create the EmulationFragment | ||
| 90 | emulationFragment = | ||
| 91 | supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? | ||
| 92 | if (emulationFragment == null) { | ||
| 93 | emulationFragment = EmulationFragment.newInstance(game) | ||
| 94 | supportFragmentManager.beginTransaction() | ||
| 95 | .add(R.id.frame_emulation_fragment, emulationFragment!!) | ||
| 96 | .commit() | ||
| 97 | } | ||
| 98 | title = game.title | ||
| 99 | |||
| 100 | nfcReader = NfcReader(this) | 99 | nfcReader = NfcReader(this) |
| 101 | nfcReader.initialize() | 100 | nfcReader.initialize() |
| 102 | 101 | ||
| 103 | inputHandler = InputHandler() | 102 | inputHandler = InputHandler() |
| 104 | inputHandler.initialize() | 103 | inputHandler.initialize() |
| 105 | 104 | ||
| 106 | lifecycleScope.launch(Dispatchers.Main) { | ||
| 107 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 108 | WindowInfoTracker.getOrCreate(this@EmulationActivity) | ||
| 109 | .windowLayoutInfo(this@EmulationActivity) | ||
| 110 | .collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | // Start a foreground service to prevent the app from getting killed in the background | 105 | // Start a foreground service to prevent the app from getting killed in the background |
| 115 | val startIntent = Intent(this, ForegroundService::class.java) | 106 | val startIntent = Intent(this, ForegroundService::class.java) |
| 116 | startForegroundService(startIntent) | 107 | startForegroundService(startIntent) |
| @@ -143,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 143 | super.onResume() | 134 | super.onResume() |
| 144 | nfcReader.startScanning() | 135 | nfcReader.startScanning() |
| 145 | startMotionSensorListener() | 136 | startMotionSensorListener() |
| 137 | |||
| 138 | buildPictureInPictureParams() | ||
| 146 | } | 139 | } |
| 147 | 140 | ||
| 148 | override fun onPause() { | 141 | override fun onPause() { |
| @@ -151,17 +144,22 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 151 | stopMotionSensorListener() | 144 | stopMotionSensorListener() |
| 152 | } | 145 | } |
| 153 | 146 | ||
| 147 | override fun onUserLeaveHint() { | ||
| 148 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { | ||
| 149 | if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) { | ||
| 150 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | ||
| 151 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | ||
| 152 | enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 154 | override fun onNewIntent(intent: Intent) { | 157 | override fun onNewIntent(intent: Intent) { |
| 155 | super.onNewIntent(intent) | 158 | super.onNewIntent(intent) |
| 156 | setIntent(intent) | 159 | setIntent(intent) |
| 157 | nfcReader.onNewIntent(intent) | 160 | nfcReader.onNewIntent(intent) |
| 158 | } | 161 | } |
| 159 | 162 | ||
| 160 | override fun onSaveInstanceState(outState: Bundle) { | ||
| 161 | outState.putParcelable(EXTRA_SELECTED_GAME, game) | ||
| 162 | super.onSaveInstanceState(outState) | ||
| 163 | } | ||
| 164 | |||
| 165 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { | 163 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { |
| 166 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && | 164 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && |
| 167 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD | 165 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD |
| @@ -248,10 +246,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 248 | 246 | ||
| 249 | override fun onAccuracyChanged(sensor: Sensor, i: Int) {} | 247 | override fun onAccuracyChanged(sensor: Sensor, i: Int) {} |
| 250 | 248 | ||
| 251 | private fun restoreState(savedInstanceState: Bundle) { | ||
| 252 | game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! | ||
| 253 | } | ||
| 254 | |||
| 255 | private fun enableFullscreenImmersive() { | 249 | private fun enableFullscreenImmersive() { |
| 256 | WindowCompat.setDecorFitsSystemWindows(window, false) | 250 | WindowCompat.setDecorFitsSystemWindows(window, false) |
| 257 | 251 | ||
| @@ -262,6 +256,100 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 262 | } | 256 | } |
| 263 | } | 257 | } |
| 264 | 258 | ||
| 259 | private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): | ||
| 260 | PictureInPictureParams.Builder { | ||
| 261 | val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 262 | 0 -> Rational(16, 9) | ||
| 263 | 1 -> Rational(4, 3) | ||
| 264 | 2 -> Rational(21, 9) | ||
| 265 | 3 -> Rational(16, 10) | ||
| 266 | else -> null // Best fit | ||
| 267 | } | ||
| 268 | return this.apply { aspectRatio?.let { setAspectRatio(it) } } | ||
| 269 | } | ||
| 270 | |||
| 271 | private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): | ||
| 272 | PictureInPictureParams.Builder { | ||
| 273 | val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() | ||
| 274 | val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | ||
| 275 | |||
| 276 | if (NativeLibrary.isPaused()) { | ||
| 277 | val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play) | ||
| 278 | val playPendingIntent = PendingIntent.getBroadcast( | ||
| 279 | this@EmulationActivity, | ||
| 280 | R.drawable.ic_pip_play, | ||
| 281 | Intent(actionPlay), | ||
| 282 | pendingFlags | ||
| 283 | ) | ||
| 284 | val playRemoteAction = RemoteAction( | ||
| 285 | playIcon, | ||
| 286 | getString(R.string.play), | ||
| 287 | getString(R.string.play), | ||
| 288 | playPendingIntent | ||
| 289 | ) | ||
| 290 | pictureInPictureActions.add(playRemoteAction) | ||
| 291 | } else { | ||
| 292 | val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause) | ||
| 293 | val pausePendingIntent = PendingIntent.getBroadcast( | ||
| 294 | this@EmulationActivity, | ||
| 295 | R.drawable.ic_pip_pause, | ||
| 296 | Intent(actionPause), | ||
| 297 | pendingFlags | ||
| 298 | ) | ||
| 299 | val pauseRemoteAction = RemoteAction( | ||
| 300 | pauseIcon, | ||
| 301 | getString(R.string.pause), | ||
| 302 | getString(R.string.pause), | ||
| 303 | pausePendingIntent | ||
| 304 | ) | ||
| 305 | pictureInPictureActions.add(pauseRemoteAction) | ||
| 306 | } | ||
| 307 | |||
| 308 | return this.apply { setActions(pictureInPictureActions) } | ||
| 309 | } | ||
| 310 | |||
| 311 | fun buildPictureInPictureParams() { | ||
| 312 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | ||
| 313 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | ||
| 314 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 315 | pictureInPictureParamsBuilder.setAutoEnterEnabled( | ||
| 316 | BooleanSetting.PICTURE_IN_PICTURE.boolean | ||
| 317 | ) | ||
| 318 | } | ||
| 319 | setPictureInPictureParams(pictureInPictureParamsBuilder.build()) | ||
| 320 | } | ||
| 321 | |||
| 322 | private var pictureInPictureReceiver = object : BroadcastReceiver() { | ||
| 323 | override fun onReceive(context: Context?, intent: Intent) { | ||
| 324 | if (intent.action == actionPlay) { | ||
| 325 | if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation() | ||
| 326 | } else if (intent.action == actionPause) { | ||
| 327 | if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() | ||
| 328 | } | ||
| 329 | buildPictureInPictureParams() | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | override fun onPictureInPictureModeChanged( | ||
| 334 | isInPictureInPictureMode: Boolean, | ||
| 335 | newConfig: Configuration | ||
| 336 | ) { | ||
| 337 | super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) | ||
| 338 | if (isInPictureInPictureMode) { | ||
| 339 | IntentFilter().apply { | ||
| 340 | addAction(actionPause) | ||
| 341 | addAction(actionPlay) | ||
| 342 | }.also { | ||
| 343 | registerReceiver(pictureInPictureReceiver, it) | ||
| 344 | } | ||
| 345 | } else { | ||
| 346 | try { | ||
| 347 | unregisterReceiver(pictureInPictureReceiver) | ||
| 348 | } catch (ignored: Exception) { | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 265 | private fun startMotionSensorListener() { | 353 | private fun startMotionSensorListener() { |
| 266 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager | 354 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager |
| 267 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) | 355 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 7f9e2e2d4..e91277d35 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt | |||
| @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 16 | import androidx.documentfile.provider.DocumentFile | 16 | import androidx.documentfile.provider.DocumentFile |
| 17 | import androidx.lifecycle.ViewModelProvider | 17 | import androidx.lifecycle.ViewModelProvider |
| 18 | import androidx.lifecycle.lifecycleScope | 18 | import androidx.lifecycle.lifecycleScope |
| 19 | import androidx.navigation.findNavController | ||
| 19 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
| 20 | import androidx.recyclerview.widget.AsyncDifferConfig | 21 | import androidx.recyclerview.widget.AsyncDifferConfig |
| 21 | import androidx.recyclerview.widget.DiffUtil | 22 | import androidx.recyclerview.widget.DiffUtil |
| @@ -23,13 +24,13 @@ import androidx.recyclerview.widget.ListAdapter | |||
| 23 | import androidx.recyclerview.widget.RecyclerView | 24 | import androidx.recyclerview.widget.RecyclerView |
| 24 | import coil.load | 25 | import coil.load |
| 25 | import kotlinx.coroutines.launch | 26 | import kotlinx.coroutines.launch |
| 27 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 26 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 27 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 28 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 29 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 30 | import org.yuzu.yuzu_emu.activities.EmulationActivity | ||
| 31 | import org.yuzu.yuzu_emu.model.Game | 33 | import org.yuzu.yuzu_emu.model.Game |
| 32 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 33 | import org.yuzu.yuzu_emu.model.GamesViewModel | 34 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 34 | 35 | ||
| 35 | class GameAdapter(private val activity: AppCompatActivity) : | 36 | class GameAdapter(private val activity: AppCompatActivity) : |
| @@ -58,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 58 | override fun onClick(view: View) { | 59 | override fun onClick(view: View) { |
| 59 | val holder = view.tag as GameViewHolder | 60 | val holder = view.tag as GameViewHolder |
| 60 | 61 | ||
| 61 | val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true | 62 | val gameExists = DocumentFile.fromSingleUri( |
| 63 | YuzuApplication.appContext, | ||
| 64 | Uri.parse(holder.game.path) | ||
| 65 | )?.exists() == true | ||
| 62 | if (!gameExists) { | 66 | if (!gameExists) { |
| 63 | Toast.makeText( | 67 | Toast.makeText( |
| 64 | YuzuApplication.appContext, | 68 | YuzuApplication.appContext, |
| @@ -78,7 +82,8 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 78 | ) | 82 | ) |
| 79 | .apply() | 83 | .apply() |
| 80 | 84 | ||
| 81 | EmulationActivity.launch(activity, holder.game) | 85 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) |
| 86 | view.findNavController().navigate(action) | ||
| 82 | } | 87 | } |
| 83 | 88 | ||
| 84 | inner class GameViewHolder(val binding: CardGameBinding) : | 89 | inner class GameViewHolder(val binding: CardGameBinding) : |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index b719dd539..d3df3bc81 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 58 | ) | 58 | ) |
| 59 | 59 | ||
| 60 | when (option.titleId) { | 60 | when (option.titleId) { |
| 61 | R.string.get_early_access -> binding.optionLayout.background = | 61 | R.string.get_early_access -> |
| 62 | ContextCompat.getDrawable( | 62 | binding.optionLayout.background = |
| 63 | binding.optionCard.context, | 63 | ContextCompat.getDrawable( |
| 64 | R.drawable.premium_background | 64 | binding.optionCard.context, |
| 65 | ) | 65 | R.drawable.premium_background |
| 66 | ) | ||
| 66 | } | 67 | } |
| 67 | } | 68 | } |
| 68 | } | 69 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt index 82a6712b6..e058067c9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt | |||
| @@ -12,10 +12,10 @@ import android.view.WindowInsets | |||
| 12 | import android.view.inputmethod.InputMethodManager | 12 | import android.view.inputmethod.InputMethodManager |
| 13 | import androidx.annotation.Keep | 13 | import androidx.annotation.Keep |
| 14 | import androidx.core.view.ViewCompat | 14 | import androidx.core.view.ViewCompat |
| 15 | import java.io.Serializable | ||
| 15 | import org.yuzu.yuzu_emu.NativeLibrary | 16 | import org.yuzu.yuzu_emu.NativeLibrary |
| 16 | import org.yuzu.yuzu_emu.R | 17 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment | 18 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment |
| 18 | import java.io.Serializable | ||
| 19 | 19 | ||
| 20 | @Keep | 20 | @Keep |
| 21 | object SoftwareKeyboard { | 21 | object SoftwareKeyboard { |
| @@ -40,19 +40,22 @@ object SoftwareKeyboard { | |||
| 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. | 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. |
| 41 | val handler = Handler(Looper.myLooper()!!) | 41 | val handler = Handler(Looper.myLooper()!!) |
| 42 | val delayMs = 500 | 42 | val delayMs = 500 |
| 43 | handler.postDelayed(object : Runnable { | 43 | handler.postDelayed( |
| 44 | override fun run() { | 44 | object : Runnable { |
| 45 | val insets = ViewCompat.getRootWindowInsets(overlayView) | 45 | override fun run() { |
| 46 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) | 46 | val insets = ViewCompat.getRootWindowInsets(overlayView) |
| 47 | if (isKeyboardVisible) { | 47 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) |
| 48 | handler.postDelayed(this, delayMs.toLong()) | 48 | if (isKeyboardVisible) { |
| 49 | return | 49 | handler.postDelayed(this, delayMs.toLong()) |
| 50 | } | 50 | return |
| 51 | } | ||
| 51 | 52 | ||
| 52 | // No longer visible, submit the result. | 53 | // No longer visible, submit the result. |
| 53 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) | 54 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) |
| 54 | } | 55 | } |
| 55 | }, delayMs.toLong()) | 56 | }, |
| 57 | delayMs.toLong() | ||
| 58 | ) | ||
| 56 | } | 59 | } |
| 57 | 60 | ||
| 58 | @JvmStatic | 61 | @JvmStatic |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt index 3b1559c80..a18efef19 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt | |||
| @@ -20,7 +20,10 @@ object DiskShaderCacheProgress { | |||
| 20 | emulationActivity.getString(R.string.loading), | 20 | emulationActivity.getString(R.string.loading), |
| 21 | emulationActivity.getString(R.string.preparing_shaders) | 21 | emulationActivity.getString(R.string.preparing_shaders) |
| 22 | ) | 22 | ) |
| 23 | fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) | 23 | fragment.show( |
| 24 | emulationActivity.supportFragmentManager, | ||
| 25 | ShaderProgressDialogFragment.TAG | ||
| 26 | ) | ||
| 24 | } | 27 | } |
| 25 | synchronized(finishLock) { finishLock.wait() } | 28 | synchronized(finishLock) { finishLock.wait() } |
| 26 | } | 29 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt index 2c68c9ac3..8a8e0a6e8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt | |||
| @@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() { | |||
| 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> |
| 63 | alertDialog.setMessage(msg) | 63 | alertDialog.setMessage(msg) |
| 64 | } | 64 | } |
| 65 | synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } | 65 | synchronized(DiskShaderCacheProgress.finishLock) { |
| 66 | DiskShaderCacheProgress.finishLock.notifyAll() | ||
| 67 | } | ||
| 66 | } | 68 | } |
| 67 | 69 | ||
| 68 | override fun onDestroyView() { | 70 | override fun onDestroyView() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt index 4c3a9ca80..f3be156b5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt | |||
| @@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor | |||
| 13 | import android.provider.DocumentsContract | 13 | import android.provider.DocumentsContract |
| 14 | import android.provider.DocumentsProvider | 14 | import android.provider.DocumentsProvider |
| 15 | import android.webkit.MimeTypeMap | 15 | import android.webkit.MimeTypeMap |
| 16 | import java.io.* | ||
| 16 | import org.yuzu.yuzu_emu.BuildConfig | 17 | import org.yuzu.yuzu_emu.BuildConfig |
| 17 | import org.yuzu.yuzu_emu.R | 18 | import org.yuzu.yuzu_emu.R |
| 18 | import org.yuzu.yuzu_emu.YuzuApplication | 19 | import org.yuzu.yuzu_emu.YuzuApplication |
| 19 | import org.yuzu.yuzu_emu.getPublicFilesDir | 20 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 20 | import java.io.* | ||
| 21 | 21 | ||
| 22 | class DocumentProvider : DocumentsProvider() { | 22 | class DocumentProvider : DocumentsProvider() { |
| 23 | private val baseDirectory: File | 23 | private val baseDirectory: File |
| @@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() { | |||
| 44 | DocumentsContract.Document.COLUMN_SIZE | 44 | DocumentsContract.Document.COLUMN_SIZE |
| 45 | ) | 45 | ) |
| 46 | 46 | ||
| 47 | const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user" | 47 | const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user" |
| 48 | const val ROOT_ID: String = "root" | 48 | const val ROOT_ID: String = "root" |
| 49 | } | 49 | } |
| 50 | 50 | ||
| @@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() { | |||
| 58 | private fun getFile(documentId: String): File { | 58 | private fun getFile(documentId: String): File { |
| 59 | if (documentId.startsWith(ROOT_ID)) { | 59 | if (documentId.startsWith(ROOT_ID)) { |
| 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) | 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) |
| 61 | if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") | 61 | if (!file.exists()) { |
| 62 | throw FileNotFoundException( | ||
| 63 | "${file.absolutePath} ($documentId) not found" | ||
| 64 | ) | ||
| 65 | } | ||
| 62 | return file | 66 | return file |
| 63 | } else { | 67 | } else { |
| 64 | throw FileNotFoundException("'$documentId' is not in any known root") | 68 | throw FileNotFoundException("'$documentId' is not in any known root") |
| @@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() { | |||
| 80 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) | 84 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) |
| 81 | add( | 85 | add( |
| 82 | DocumentsContract.Root.COLUMN_FLAGS, | 86 | DocumentsContract.Root.COLUMN_FLAGS, |
| 83 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | 87 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or |
| 88 | DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | ||
| 84 | ) | 89 | ) |
| 85 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) | 90 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) |
| 86 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) | 91 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) |
| @@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 127 | 132 | ||
| 128 | try { | 133 | try { |
| 129 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { | 134 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { |
| 130 | if (!newFile.mkdir()) | 135 | if (!newFile.mkdir()) { |
| 131 | throw IOException("Failed to create directory") | 136 | throw IOException("Failed to create directory") |
| 137 | } | ||
| 132 | } else { | 138 | } else { |
| 133 | if (!newFile.createNewFile()) | 139 | if (!newFile.createNewFile()) { |
| 134 | throw IOException("Failed to create file") | 140 | throw IOException("Failed to create file") |
| 141 | } | ||
| 135 | } | 142 | } |
| 136 | } catch (e: IOException) { | 143 | } catch (e: IOException) { |
| 137 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") | 144 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") |
| @@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 142 | 149 | ||
| 143 | override fun deleteDocument(documentId: String?) { | 150 | override fun deleteDocument(documentId: String?) { |
| 144 | val file = getFile(documentId!!) | 151 | val file = getFile(documentId!!) |
| 145 | if (!file.delete()) | 152 | if (!file.delete()) { |
| 146 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 153 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 154 | } | ||
| 147 | } | 155 | } |
| 148 | 156 | ||
| 149 | override fun removeDocument(documentId: String, parentDocumentId: String?) { | 157 | override fun removeDocument(documentId: String, parentDocumentId: String?) { |
| @@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() { | |||
| 151 | val file = getFile(documentId) | 159 | val file = getFile(documentId) |
| 152 | 160 | ||
| 153 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { | 161 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { |
| 154 | if (!file.delete()) | 162 | if (!file.delete()) { |
| 155 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 163 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 164 | } | ||
| 156 | } else { | 165 | } else { |
| 157 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 166 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 158 | } | 167 | } |
| 159 | } | 168 | } |
| 160 | 169 | ||
| 161 | override fun renameDocument(documentId: String?, displayName: String?): String { | 170 | override fun renameDocument(documentId: String?, displayName: String?): String { |
| 162 | if (displayName == null) | 171 | if (displayName == null) { |
| 163 | throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") | 172 | throw FileNotFoundException( |
| 173 | "Couldn't rename document '$documentId' as the new name is null" | ||
| 174 | ) | ||
| 175 | } | ||
| 164 | 176 | ||
| 165 | val sourceFile = getFile(documentId!!) | 177 | val sourceFile = getFile(documentId!!) |
| 166 | val sourceParentFile = sourceFile.parentFile | 178 | val sourceParentFile = sourceFile.parentFile |
| 167 | ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") | 179 | ?: throw FileNotFoundException( |
| 180 | "Couldn't rename document '$documentId' as it has no parent" | ||
| 181 | ) | ||
| 168 | val destFile = sourceParentFile.resolve(displayName) | 182 | val destFile = sourceParentFile.resolve(displayName) |
| 169 | 183 | ||
| 170 | try { | 184 | try { |
| 171 | if (!sourceFile.renameTo(destFile)) | 185 | if (!sourceFile.renameTo(destFile)) { |
| 172 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") | 186 | throw FileNotFoundException( |
| 187 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'" | ||
| 188 | ) | ||
| 189 | } | ||
| 173 | } catch (e: Exception) { | 190 | } catch (e: Exception) { |
| 174 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") | 191 | throw FileNotFoundException( |
| 192 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " + | ||
| 193 | "${e.message}" | ||
| 194 | ) | ||
| 175 | } | 195 | } |
| 176 | 196 | ||
| 177 | return getDocumentId(destFile) | 197 | return getDocumentId(destFile) |
| 178 | } | 198 | } |
| 179 | 199 | ||
| 180 | private fun copyDocument( | 200 | private fun copyDocument( |
| 181 | sourceDocumentId: String, sourceParentDocumentId: String, | 201 | sourceDocumentId: String, |
| 202 | sourceParentDocumentId: String, | ||
| 182 | targetParentDocumentId: String? | 203 | targetParentDocumentId: String? |
| 183 | ): String { | 204 | ): String { |
| 184 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) | 205 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { |
| 185 | throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") | 206 | throw FileNotFoundException( |
| 207 | "Couldn't copy document '$sourceDocumentId' as its parent is not " + | ||
| 208 | "'$sourceParentDocumentId'" | ||
| 209 | ) | ||
| 210 | } | ||
| 186 | 211 | ||
| 187 | return copyDocument(sourceDocumentId, targetParentDocumentId) | 212 | return copyDocument(sourceDocumentId, targetParentDocumentId) |
| 188 | } | 213 | } |
| @@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 193 | val newFile = parent.resolveWithoutConflict(oldFile.name) | 218 | val newFile = parent.resolveWithoutConflict(oldFile.name) |
| 194 | 219 | ||
| 195 | try { | 220 | try { |
| 196 | if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) | 221 | if (!( |
| 222 | newFile.createNewFile() && newFile.setWritable(true) && | ||
| 223 | newFile.setReadable(true) | ||
| 224 | ) | ||
| 225 | ) { | ||
| 197 | throw IOException("Couldn't create new file") | 226 | throw IOException("Couldn't create new file") |
| 227 | } | ||
| 198 | 228 | ||
| 199 | FileInputStream(oldFile).use { inStream -> | 229 | FileInputStream(oldFile).use { inStream -> |
| 200 | FileOutputStream(newFile).use { outStream -> | 230 | FileOutputStream(newFile).use { outStream -> |
| @@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() { | |||
| 209 | } | 239 | } |
| 210 | 240 | ||
| 211 | override fun moveDocument( | 241 | override fun moveDocument( |
| 212 | sourceDocumentId: String, sourceParentDocumentId: String?, | 242 | sourceDocumentId: String, |
| 243 | sourceParentDocumentId: String?, | ||
| 213 | targetParentDocumentId: String? | 244 | targetParentDocumentId: String? |
| 214 | ): String { | 245 | ): String { |
| 215 | try { | 246 | try { |
| 216 | val newDocumentId = copyDocument( | 247 | val newDocumentId = copyDocument( |
| 217 | sourceDocumentId, sourceParentDocumentId!!, | 248 | sourceDocumentId, |
| 249 | sourceParentDocumentId!!, | ||
| 218 | targetParentDocumentId | 250 | targetParentDocumentId |
| 219 | ) | 251 | ) |
| 220 | removeDocument(sourceDocumentId, sourceParentDocumentId) | 252 | removeDocument(sourceDocumentId, sourceParentDocumentId) |
| @@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() { | |||
| 245 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) | 277 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) |
| 246 | add( | 278 | add( |
| 247 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, | 279 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, |
| 248 | if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name | 280 | if (localFile == baseDirectory) { |
| 281 | context!!.getString(R.string.app_name) | ||
| 282 | } else { | ||
| 283 | localFile.name | ||
| 284 | } | ||
| 249 | ) | 285 | ) |
| 250 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) | 286 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) |
| 251 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) | 287 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) |
| 252 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) | 288 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) |
| 253 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) | 289 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) |
| 254 | if (localFile == baseDirectory) | 290 | if (localFile == baseDirectory) { |
| 255 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) | 291 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) |
| 292 | } | ||
| 256 | } | 293 | } |
| 257 | 294 | ||
| 258 | return cursor | 295 | return cursor |
| 259 | } | 296 | } |
| 260 | 297 | ||
| 261 | private fun getTypeForFile(file: File): Any { | 298 | private fun getTypeForFile(file: File): Any { |
| 262 | return if (file.isDirectory) | 299 | return if (file.isDirectory) { |
| 263 | DocumentsContract.Document.MIME_TYPE_DIR | 300 | DocumentsContract.Document.MIME_TYPE_DIR |
| 264 | else | 301 | } else { |
| 265 | getTypeForName(file.name) | 302 | getTypeForName(file.name) |
| 303 | } | ||
| 266 | } | 304 | } |
| 267 | 305 | ||
| 268 | private fun getTypeForName(name: String): Any { | 306 | private fun getTypeForName(name: String): Any { |
| @@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 270 | if (lastDot >= 0) { | 308 | if (lastDot >= 0) { |
| 271 | val extension = name.substring(lastDot + 1) | 309 | val extension = name.substring(lastDot + 1) |
| 272 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) | 310 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) |
| 273 | if (mime != null) | 311 | if (mime != null) { |
| 274 | return mime | 312 | return mime |
| 313 | } | ||
| 275 | } | 314 | } |
| 276 | return "application/octect-stream" | 315 | return "application/octect-stream" |
| 277 | } | 316 | } |
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 3dfd66779..d41933766 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 | |||
| @@ -8,6 +8,10 @@ enum class BooleanSetting( | |||
| 8 | override val section: String, | 8 | override val section: String, |
| 9 | override val defaultValue: Boolean | 9 | override val defaultValue: Boolean |
| 10 | ) : AbstractBooleanSetting { | 10 | ) : AbstractBooleanSetting { |
| 11 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), | ||
| 12 | FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), | ||
| 13 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), | ||
| 14 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), | ||
| 11 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); | 15 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); |
| 12 | 16 | ||
| 13 | override var boolean: Boolean = defaultValue | 17 | override var boolean: Boolean = defaultValue |
| @@ -27,6 +31,7 @@ enum class BooleanSetting( | |||
| 27 | 31 | ||
| 28 | companion object { | 32 | companion object { |
| 29 | private val NOT_RUNTIME_EDITABLE = listOf( | 33 | private val NOT_RUNTIME_EDITABLE = listOf( |
| 34 | PICTURE_IN_PICTURE, | ||
| 30 | USE_CUSTOM_RTC | 35 | USE_CUSTOM_RTC |
| 31 | ) | 36 | ) |
| 32 | 37 | ||
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 fa84f94f5..4427a7d9d 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 | |||
| @@ -93,6 +93,11 @@ enum class IntSetting( | |||
| 93 | Settings.SECTION_RENDERER, | 93 | Settings.SECTION_RENDERER, |
| 94 | 0 | 94 | 0 |
| 95 | ), | 95 | ), |
| 96 | RENDERER_SCREEN_LAYOUT( | ||
| 97 | "screen_layout", | ||
| 98 | Settings.SECTION_RENDERER, | ||
| 99 | Settings.LayoutOption_MobileLandscape | ||
| 100 | ), | ||
| 96 | RENDERER_ASPECT_RATIO( | 101 | RENDERER_ASPECT_RATIO( |
| 97 | "aspect_ratio", | 102 | "aspect_ratio", |
| 98 | Settings.SECTION_RENDERER, | 103 | Settings.SECTION_RENDERER, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 8df20b928..88afb2223 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt | |||
| @@ -4,11 +4,11 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | 6 | import android.text.TextUtils |
| 7 | import java.util.* | ||
| 7 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | 9 | import org.yuzu.yuzu_emu.YuzuApplication |
| 9 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView |
| 10 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 11 | import java.util.* | ||
| 12 | 12 | ||
| 13 | class Settings { | 13 | class Settings { |
| 14 | private var gameId: String? = null | 14 | private var gameId: String? = null |
| @@ -133,7 +133,6 @@ class Settings { | |||
| 133 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | 133 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" |
| 134 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | 134 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" |
| 135 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | 135 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" |
| 136 | const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout" | ||
| 137 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | 136 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" |
| 138 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | 137 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" |
| 139 | 138 | ||
| @@ -144,6 +143,10 @@ class Settings { | |||
| 144 | 143 | ||
| 145 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() | 144 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() |
| 146 | 145 | ||
| 146 | const val LayoutOption_Unspecified = 0 | ||
| 147 | const val LayoutOption_MobilePortrait = 4 | ||
| 148 | const val LayoutOption_MobileLandscape = 5 | ||
| 149 | |||
| 147 | init { | 150 | init { |
| 148 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = | 151 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = |
| 149 | listOf( | 152 | listOf( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 63f95690c..6621289fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt | |||
| @@ -8,6 +8,7 @@ enum class StringSetting( | |||
| 8 | override val section: String, | 8 | override val section: String, |
| 9 | override val defaultValue: String | 9 | override val defaultValue: String |
| 10 | ) : AbstractStringSetting { | 10 | ) : AbstractStringSetting { |
| 11 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), | ||
| 11 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); | 12 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); |
| 12 | 13 | ||
| 13 | override var string: String = defaultValue | 14 | override var string: String = defaultValue |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt index 0f8edbfb0..a67001311 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt | |||
| @@ -3,12 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | |||
| 8 | class HeaderSetting( | 6 | class HeaderSetting( |
| 9 | setting: AbstractSetting?, | 7 | titleId: Int |
| 10 | titleId: Int, | 8 | ) : SettingsItem(null, titleId, 0) { |
| 11 | descriptionId: Int | ||
| 12 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 13 | override val type = TYPE_HEADER | 9 | override val type = TYPE_HEADER |
| 14 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 9eac9904e..7306ec458 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt | |||
| @@ -4,7 +4,6 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 8 | 7 | ||
| 9 | class SingleChoiceSetting( | 8 | class SingleChoiceSetting( |
| 10 | setting: AbstractIntSetting?, | 9 | setting: AbstractIntSetting?, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index 842648ce4..92d0167ae 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt | |||
| @@ -3,13 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import kotlin.math.roundToInt | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 11 | import org.yuzu.yuzu_emu.utils.Log | 10 | import org.yuzu.yuzu_emu.utils.Log |
| 12 | import kotlin.math.roundToInt | ||
| 13 | 11 | ||
| 14 | class SliderSetting( | 12 | class SliderSetting( |
| 15 | setting: AbstractSetting?, | 13 | setting: AbstractSetting?, |
| @@ -19,7 +17,7 @@ class SliderSetting( | |||
| 19 | val max: Int, | 17 | val max: Int, |
| 20 | val units: String, | 18 | val units: String, |
| 21 | val key: String? = null, | 19 | val key: String? = null, |
| 22 | val defaultValue: Int? = null, | 20 | val defaultValue: Int? = null |
| 23 | ) : SettingsItem(setting, titleId, descriptionId) { | 21 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 24 | override val type = TYPE_SLIDER | 22 | override val type = TYPE_SLIDER |
| 25 | 23 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 9e9b00d10..3b6731dcd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt | |||
| @@ -5,24 +5,25 @@ package org.yuzu.yuzu_emu.features.settings.model.view | |||
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 9 | 8 | ||
| 10 | class StringSingleChoiceSetting( | 9 | class StringSingleChoiceSetting( |
| 11 | val key: String? = null, | ||
| 12 | setting: AbstractSetting?, | 10 | setting: AbstractSetting?, |
| 13 | titleId: Int, | 11 | titleId: Int, |
| 14 | descriptionId: Int, | 12 | descriptionId: Int, |
| 15 | val choicesId: Array<String>, | 13 | val choices: Array<String>, |
| 16 | private val valuesId: Array<String>?, | 14 | val values: Array<String>?, |
| 15 | val key: String? = null, | ||
| 17 | private val defaultValue: String? = null | 16 | private val defaultValue: String? = null |
| 18 | ) : SettingsItem(setting, titleId, descriptionId) { | 17 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 19 | override val type = TYPE_STRING_SINGLE_CHOICE | 18 | override val type = TYPE_STRING_SINGLE_CHOICE |
| 20 | 19 | ||
| 21 | fun getValueAt(index: Int): String? { | 20 | fun getValueAt(index: Int): String? { |
| 22 | if (valuesId == null) return null | 21 | if (values == null) return null |
| 23 | return if (index >= 0 && index < valuesId.size) { | 22 | return if (index >= 0 && index < values.size) { |
| 24 | valuesId[index] | 23 | values[index] |
| 25 | } else "" | 24 | } else { |
| 25 | "" | ||
| 26 | } | ||
| 26 | } | 27 | } |
| 27 | 28 | ||
| 28 | val selectedValue: String | 29 | val selectedValue: String |
| @@ -35,8 +36,8 @@ class StringSingleChoiceSetting( | |||
| 35 | val selectValueIndex: Int | 36 | val selectValueIndex: Int |
| 36 | get() { | 37 | get() { |
| 37 | val selectedValue = selectedValue | 38 | val selectedValue = selectedValue |
| 38 | for (i in valuesId!!.indices) { | 39 | for (i in values!!.indices) { |
| 39 | if (valuesId[i] == selectedValue) { | 40 | if (values[i] == selectedValue) { |
| 40 | return i | 41 | return i |
| 41 | } | 42 | } |
| 42 | } | 43 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index a3ef59c2f..8a9d13a92 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt | |||
| @@ -3,8 +3,6 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | |||
| 8 | class SubmenuSetting( | 6 | class SubmenuSetting( |
| 9 | titleId: Int, | 7 | titleId: Int, |
| 10 | descriptionId: Int, | 8 | descriptionId: Int, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 72e2cce2a..a5af5a7ae 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt | |||
| @@ -8,17 +8,18 @@ import android.content.Intent | |||
| 8 | import android.os.Bundle | 8 | import android.os.Bundle |
| 9 | import android.view.Menu | 9 | import android.view.Menu |
| 10 | import android.view.View | 10 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | ||
| 11 | import android.widget.Toast | 12 | import android.widget.Toast |
| 13 | import androidx.activity.OnBackPressedCallback | ||
| 14 | import androidx.activity.result.ActivityResultLauncher | ||
| 12 | import androidx.activity.viewModels | 15 | import androidx.activity.viewModels |
| 13 | import androidx.appcompat.app.AppCompatActivity | 16 | import androidx.appcompat.app.AppCompatActivity |
| 14 | import androidx.core.view.ViewCompat | 17 | import androidx.core.view.ViewCompat |
| 15 | import androidx.core.view.WindowCompat | 18 | import androidx.core.view.WindowCompat |
| 16 | import androidx.core.view.WindowInsetsCompat | 19 | import androidx.core.view.WindowInsetsCompat |
| 17 | import android.view.ViewGroup.MarginLayoutParams | ||
| 18 | import androidx.activity.OnBackPressedCallback | ||
| 19 | import androidx.core.view.updatePadding | 20 | import androidx.core.view.updatePadding |
| 20 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 21 | import org.yuzu.yuzu_emu.NativeLibrary | 22 | import java.io.IOException |
| 22 | import org.yuzu.yuzu_emu.R | 23 | import org.yuzu.yuzu_emu.R |
| 23 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 24 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| @@ -29,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | |||
| 29 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting |
| 30 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 31 | import org.yuzu.yuzu_emu.utils.* | 32 | import org.yuzu.yuzu_emu.utils.* |
| 32 | import java.io.IOException | ||
| 33 | 33 | ||
| 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { | 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { |
| 35 | private val presenter = SettingsActivityPresenter(this) | 35 | private val presenter = SettingsActivityPresenter(this) |
| @@ -59,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 59 | setSupportActionBar(binding.toolbarSettings) | 59 | setSupportActionBar(binding.toolbarSettings) |
| 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) | 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) |
| 61 | 61 | ||
| 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 63 | InsetsHelper.GESTURE_NAVIGATION | ||
| 64 | ) { | ||
| 63 | binding.navigationBarShade.setBackgroundColor( | 65 | binding.navigationBarShade.setBackgroundColor( |
| 64 | ThemeHelper.getColorWithOpacity( | 66 | ThemeHelper.getColorWithOpacity( |
| 65 | MaterialColors.getColor( | 67 | MaterialColors.getColor( |
| @@ -75,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 75 | this, | 77 | this, |
| 76 | object : OnBackPressedCallback(true) { | 78 | object : OnBackPressedCallback(true) { |
| 77 | override fun handleOnBackPressed() = navigateBack() | 79 | override fun handleOnBackPressed() = navigateBack() |
| 78 | }) | 80 | } |
| 81 | ) | ||
| 79 | 82 | ||
| 80 | setInsets() | 83 | setInsets() |
| 81 | } | 84 | } |
| @@ -148,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 148 | private fun areSystemAnimationsEnabled(): Boolean { | 151 | private fun areSystemAnimationsEnabled(): Boolean { |
| 149 | val duration = android.provider.Settings.Global.getFloat( | 152 | val duration = android.provider.Settings.Global.getFloat( |
| 150 | contentResolver, | 153 | contentResolver, |
| 151 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f | 154 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, |
| 155 | 1f | ||
| 152 | ) | 156 | ) |
| 153 | val transition = android.provider.Settings.Global.getFloat( | 157 | val transition = android.provider.Settings.Global.getFloat( |
| 154 | contentResolver, | 158 | contentResolver, |
| 155 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f | 159 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, |
| 160 | 1f | ||
| 156 | ) | 161 | ) |
| 157 | return duration != 0f && transition != 0f | 162 | return duration != 0f && transition != 0f |
| 158 | } | 163 | } |
| @@ -207,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 207 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? | 212 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? |
| 208 | 213 | ||
| 209 | private fun setInsets() { | 214 | private fun setInsets() { |
| 210 | ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat -> | 215 | ViewCompat.setOnApplyWindowInsetsListener( |
| 216 | binding.frameContent | ||
| 217 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 211 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 218 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 212 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 219 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 213 | view.updatePadding( | 220 | view.updatePadding( |
| @@ -239,5 +246,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 239 | settings.putExtra(ARG_GAME_ID, gameId) | 246 | settings.putExtra(ARG_GAME_ID, gameId) |
| 240 | context.startActivity(settings) | 247 | context.startActivity(settings) |
| 241 | } | 248 | } |
| 249 | |||
| 250 | fun launch( | ||
| 251 | context: Context, | ||
| 252 | launcher: ActivityResultLauncher<Intent>, | ||
| 253 | menuTag: String?, | ||
| 254 | gameId: String? | ||
| 255 | ) { | ||
| 256 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 257 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 258 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 259 | launcher.launch(settings) | ||
| 260 | } | ||
| 242 | } | 261 | } |
| 243 | } | 262 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt index 4361d95fb..93e677b21 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui | |||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import java.io.File | ||
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | 10 | import org.yuzu.yuzu_emu.NativeLibrary |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 11 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 12 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 13 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 13 | import org.yuzu.yuzu_emu.utils.Log | 14 | import org.yuzu.yuzu_emu.utils.Log |
| 14 | import java.io.File | ||
| 15 | 15 | ||
| 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { | 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { |
| 17 | val settings: Settings get() = activityView.settings | 17 | val settings: Settings get() = activityView.settings |
| @@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) | |||
| 46 | 46 | ||
| 47 | private fun prepareDirectoriesIfNeeded() { | 47 | private fun prepareDirectoriesIfNeeded() { |
| 48 | val configFile = | 48 | val configFile = |
| 49 | File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 49 | File( |
| 50 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 51 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 52 | ) | ||
| 50 | if (!configFile.exists()) { | 53 | if (!configFile.exists()) { |
| 51 | Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 54 | Log.error( |
| 55 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 56 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 57 | ) | ||
| 52 | Log.error("yuzu config file could not be found!") | 58 | Log.error("yuzu config file could not be found!") |
| 53 | } | 59 | } |
| 54 | 60 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 1eb4899fc..ce0b92c90 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt | |||
| @@ -13,7 +13,6 @@ import android.view.ViewGroup | |||
| 13 | import android.widget.TextView | 13 | import android.widget.TextView |
| 14 | import androidx.appcompat.app.AlertDialog | 14 | import androidx.appcompat.app.AlertDialog |
| 15 | import androidx.appcompat.app.AppCompatActivity | 15 | import androidx.appcompat.app.AppCompatActivity |
| 16 | import androidx.fragment.app.setFragmentResultListener | ||
| 17 | import androidx.recyclerview.widget.RecyclerView | 16 | import androidx.recyclerview.widget.RecyclerView |
| 18 | import com.google.android.material.datepicker.MaterialDatePicker | 17 | import com.google.android.material.datepicker.MaterialDatePicker |
| 19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 18 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| @@ -139,7 +138,7 @@ class SettingsAdapter( | |||
| 139 | clickedItem = item | 138 | clickedItem = item |
| 140 | dialog = MaterialAlertDialogBuilder(context) | 139 | dialog = MaterialAlertDialogBuilder(context) |
| 141 | .setTitle(item.nameId) | 140 | .setTitle(item.nameId) |
| 142 | .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this) | 141 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) |
| 143 | .show() | 142 | .show() |
| 144 | } | 143 | } |
| 145 | 144 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 867147950..70a74c4dd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt | |||
| @@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 50 | 50 | ||
| 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) | 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) |
| 53 | val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL) | 53 | val dividerDecoration = MaterialDividerItemDecoration( |
| 54 | requireContext(), | ||
| 55 | LinearLayoutManager.VERTICAL | ||
| 56 | ) | ||
| 54 | dividerDecoration.isLastItemDecorated = false | 57 | dividerDecoration.isLastItemDecorated = false |
| 55 | binding.listSettings.apply { | 58 | binding.listSettings.apply { |
| 56 | adapter = settingsAdapter | 59 | adapter = settingsAdapter |
| @@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 99 | } | 102 | } |
| 100 | 103 | ||
| 101 | private fun setInsets() { | 104 | private fun setInsets() { |
| 102 | ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat -> | 105 | ViewCompat.setOnApplyWindowInsetsListener( |
| 106 | binding.listSettings | ||
| 107 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 103 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 108 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 104 | view.updatePadding(bottom = insets.bottom) | 109 | view.updatePadding(bottom = insets.bottom) |
| 105 | windowInsets | 110 | windowInsets |
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 1ceaa6fb4..59c1d9d54 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 | |||
| @@ -7,7 +7,6 @@ import android.content.SharedPreferences | |||
| 7 | import android.os.Build | 7 | import android.os.Build |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import androidx.preference.PreferenceManager | 9 | import androidx.preference.PreferenceManager |
| 10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 11 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 11 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
| @@ -43,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 43 | } | 42 | } |
| 44 | 43 | ||
| 45 | fun putSetting(setting: AbstractSetting) { | 44 | fun putSetting(setting: AbstractSetting) { |
| 46 | if (setting.section == null) { | 45 | if (setting.section == null || setting.key == null) { |
| 47 | return | 46 | return |
| 48 | } | 47 | } |
| 49 | 48 | ||
| @@ -166,6 +165,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 166 | IntSetting.CPU_ACCURACY.defaultValue | 165 | IntSetting.CPU_ACCURACY.defaultValue |
| 167 | ) | 166 | ) |
| 168 | ) | 167 | ) |
| 168 | add( | ||
| 169 | SwitchSetting( | ||
| 170 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 171 | R.string.picture_in_picture, | ||
| 172 | R.string.picture_in_picture_description, | ||
| 173 | BooleanSetting.PICTURE_IN_PICTURE.key, | ||
| 174 | BooleanSetting.PICTURE_IN_PICTURE.defaultValue | ||
| 175 | ) | ||
| 176 | ) | ||
| 169 | } | 177 | } |
| 170 | } | 178 | } |
| 171 | 179 | ||
| @@ -227,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 227 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { | 235 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { |
| 228 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) | 236 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) |
| 229 | sl.apply { | 237 | sl.apply { |
| 230 | |||
| 231 | add( | 238 | add( |
| 232 | SingleChoiceSetting( | 239 | SingleChoiceSetting( |
| 233 | IntSetting.RENDERER_ACCURACY, | 240 | IntSetting.RENDERER_ACCURACY, |
| @@ -285,6 +292,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 285 | ) | 292 | ) |
| 286 | add( | 293 | add( |
| 287 | SingleChoiceSetting( | 294 | SingleChoiceSetting( |
| 295 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 296 | R.string.renderer_screen_layout, | ||
| 297 | 0, | ||
| 298 | R.array.rendererScreenLayoutNames, | ||
| 299 | R.array.rendererScreenLayoutValues, | ||
| 300 | IntSetting.RENDERER_SCREEN_LAYOUT.key, | ||
| 301 | IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue | ||
| 302 | ) | ||
| 303 | ) | ||
| 304 | add( | ||
| 305 | SingleChoiceSetting( | ||
| 288 | IntSetting.RENDERER_ASPECT_RATIO, | 306 | IntSetting.RENDERER_ASPECT_RATIO, |
| 289 | R.string.renderer_aspect_ratio, | 307 | R.string.renderer_aspect_ratio, |
| 290 | 0, | 308 | 0, |
| @@ -335,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 335 | 353 | ||
| 336 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { | 354 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { |
| 337 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) | 355 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) |
| 338 | sl.add( | 356 | sl.apply { |
| 339 | SliderSetting( | 357 | add( |
| 340 | IntSetting.AUDIO_VOLUME, | 358 | StringSingleChoiceSetting( |
| 341 | R.string.audio_volume, | 359 | StringSetting.AUDIO_OUTPUT_ENGINE, |
| 342 | R.string.audio_volume_description, | 360 | R.string.audio_output_engine, |
| 343 | 0, | 361 | 0, |
| 344 | 100, | 362 | settingsActivity.resources.getStringArray(R.array.outputEngineEntries), |
| 345 | "%", | 363 | settingsActivity.resources.getStringArray(R.array.outputEngineValues), |
| 346 | IntSetting.AUDIO_VOLUME.key, | 364 | StringSetting.AUDIO_OUTPUT_ENGINE.key, |
| 347 | IntSetting.AUDIO_VOLUME.defaultValue | 365 | StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue |
| 348 | ) | 366 | ) |
| 349 | ) | 367 | ) |
| 368 | add( | ||
| 369 | SliderSetting( | ||
| 370 | IntSetting.AUDIO_VOLUME, | ||
| 371 | R.string.audio_volume, | ||
| 372 | R.string.audio_volume_description, | ||
| 373 | 0, | ||
| 374 | 100, | ||
| 375 | "%", | ||
| 376 | IntSetting.AUDIO_VOLUME.key, | ||
| 377 | IntSetting.AUDIO_VOLUME.defaultValue | ||
| 378 | ) | ||
| 379 | ) | ||
| 380 | } | ||
| 350 | } | 381 | } |
| 351 | 382 | ||
| 352 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { | 383 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { |
| @@ -449,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 449 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { | 480 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { |
| 450 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) | 481 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) |
| 451 | sl.apply { | 482 | sl.apply { |
| 483 | add(HeaderSetting(R.string.gpu)) | ||
| 452 | add( | 484 | add( |
| 453 | SingleChoiceSetting( | 485 | SingleChoiceSetting( |
| 454 | IntSetting.RENDERER_BACKEND, | 486 | IntSetting.RENDERER_BACKEND, |
| @@ -469,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 469 | IntSetting.RENDERER_DEBUG.defaultValue | 501 | IntSetting.RENDERER_DEBUG.defaultValue |
| 470 | ) | 502 | ) |
| 471 | ) | 503 | ) |
| 504 | |||
| 505 | add(HeaderSetting(R.string.cpu)) | ||
| 506 | add( | ||
| 507 | SwitchSetting( | ||
| 508 | BooleanSetting.CPU_DEBUG_MODE, | ||
| 509 | R.string.cpu_debug_mode, | ||
| 510 | R.string.cpu_debug_mode_description, | ||
| 511 | BooleanSetting.CPU_DEBUG_MODE.key, | ||
| 512 | BooleanSetting.CPU_DEBUG_MODE.defaultValue | ||
| 513 | ) | ||
| 514 | ) | ||
| 515 | |||
| 516 | val fastmem = object : AbstractBooleanSetting { | ||
| 517 | override var boolean: Boolean | ||
| 518 | get() = | ||
| 519 | BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean | ||
| 520 | set(value) { | ||
| 521 | BooleanSetting.FASTMEM.boolean = value | ||
| 522 | BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value | ||
| 523 | } | ||
| 524 | override val key: String? = null | ||
| 525 | override val section: String = Settings.SECTION_CPU | ||
| 526 | override val isRuntimeEditable: Boolean = false | ||
| 527 | override val valueAsString: String = "" | ||
| 528 | override val defaultValue: Any = true | ||
| 529 | } | ||
| 530 | add( | ||
| 531 | SwitchSetting( | ||
| 532 | fastmem, | ||
| 533 | R.string.fastmem, | ||
| 534 | 0 | ||
| 535 | ) | ||
| 536 | ) | ||
| 472 | } | 537 | } |
| 473 | } | 538 | } |
| 474 | } | 539 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 04c045e77..7955532ee 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt | |||
| @@ -4,15 +4,15 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder | 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder |
| 5 | 5 | ||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 11 | import java.time.Instant | 7 | import java.time.Instant |
| 12 | import java.time.ZoneId | 8 | import java.time.ZoneId |
| 13 | import java.time.ZonedDateTime | 9 | import java.time.ZonedDateTime |
| 14 | import java.time.format.DateTimeFormatter | 10 | import java.time.format.DateTimeFormatter |
| 15 | import java.time.format.FormatStyle | 11 | import java.time.format.FormatStyle |
| 12 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 13 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 16 | 16 | ||
| 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
| 18 | SettingViewHolder(binding.root, adapter) { | 18 | SettingViewHolder(binding.root, adapter) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index de764a27f..e4e321bd3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt | |||
| @@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 26 | for (i in values.indices) { | 26 | for (i in values.indices) { |
| 27 | if (values[i] == item.selectedValue) { | 27 | if (values[i] == item.selectedValue) { |
| 28 | binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] | 28 | binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] |
| 29 | return | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } else if (item is StringSingleChoiceSetting) { | ||
| 33 | for (i in item.values!!.indices) { | ||
| 34 | if (item.values[i] == item.selectedValue) { | ||
| 35 | binding.textSettingDescription.text = item.choices[i] | ||
| 36 | return | ||
| 29 | } | 37 | } |
| 30 | } | 38 | } |
| 31 | } else { | 39 | } else { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index b163bd6ca..54f531795 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt | |||
| @@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import android.widget.CompoundButton | 7 | import android.widget.CompoundButton |
| 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
| 12 | 12 | ||
| 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : | 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index e29bca11d..70a52df5d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.utils | 4 | package org.yuzu.yuzu_emu.features.settings.utils |
| 5 | 5 | ||
| 6 | import java.io.* | ||
| 7 | import java.util.* | ||
| 6 | import org.ini4j.Wini | 8 | import org.ini4j.Wini |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | 9 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| @@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | |||
| 13 | import org.yuzu.yuzu_emu.utils.BiMap | 15 | import org.yuzu.yuzu_emu.utils.BiMap |
| 14 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 16 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 15 | import org.yuzu.yuzu_emu.utils.Log | 17 | import org.yuzu.yuzu_emu.utils.Log |
| 16 | import java.io.* | ||
| 17 | import java.util.* | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Contains static methods for interacting with .ini files in which settings are stored. | 20 | * Contains static methods for interacting with .ini files in which settings are stored. |
| @@ -137,9 +137,12 @@ object SettingsFile { | |||
| 137 | for (settingKey in sortedKeySet) { | 137 | for (settingKey in sortedKeySet) { |
| 138 | val setting = settings[settingKey] | 138 | val setting = settings[settingKey] |
| 139 | NativeLibrary.setUserSetting( | 139 | NativeLibrary.setUserSetting( |
| 140 | gameId, mapSectionNameFromIni( | 140 | gameId, |
| 141 | mapSectionNameFromIni( | ||
| 141 | section.name | 142 | section.name |
| 142 | ), setting!!.key, setting.valueAsString | 143 | ), |
| 144 | setting!!.key, | ||
| 145 | setting.valueAsString | ||
| 143 | ) | 146 | ) |
| 144 | } | 147 | } |
| 145 | } | 148 | } |
| @@ -148,13 +151,17 @@ object SettingsFile { | |||
| 148 | private fun mapSectionNameFromIni(generalSectionName: String): String? { | 151 | private fun mapSectionNameFromIni(generalSectionName: String): String? { |
| 149 | return if (sectionsMap.getForward(generalSectionName) != null) { | 152 | return if (sectionsMap.getForward(generalSectionName) != null) { |
| 150 | sectionsMap.getForward(generalSectionName) | 153 | sectionsMap.getForward(generalSectionName) |
| 151 | } else generalSectionName | 154 | } else { |
| 155 | generalSectionName | ||
| 156 | } | ||
| 152 | } | 157 | } |
| 153 | 158 | ||
| 154 | private fun mapSectionNameToIni(generalSectionName: String): String { | 159 | private fun mapSectionNameToIni(generalSectionName: String): String { |
| 155 | return if (sectionsMap.getBackward(generalSectionName) != null) { | 160 | return if (sectionsMap.getBackward(generalSectionName) != null) { |
| 156 | sectionsMap.getBackward(generalSectionName).toString() | 161 | sectionsMap.getBackward(generalSectionName).toString() |
| 157 | } else generalSectionName | 162 | } else { |
| 163 | generalSectionName | ||
| 164 | } | ||
| 158 | } | 165 | } |
| 159 | 166 | ||
| 160 | fun getSettingsFile(fileName: String): File { | 167 | fun getSettingsFile(fileName: String): File { |
| @@ -237,5 +244,21 @@ object SettingsFile { | |||
| 237 | val setting = settings[key] | 244 | val setting = settings[key] |
| 238 | parser.put(header, setting!!.key, setting.valueAsString) | 245 | parser.put(header, setting!!.key, setting.valueAsString) |
| 239 | } | 246 | } |
| 247 | |||
| 248 | BooleanSetting.values().forEach { | ||
| 249 | if (!keySet.contains(it.key)) { | ||
| 250 | parser.put(header, it.key, it.valueAsString) | ||
| 251 | } | ||
| 252 | } | ||
| 253 | IntSetting.values().forEach { | ||
| 254 | if (!keySet.contains(it.key)) { | ||
| 255 | parser.put(header, it.key, it.valueAsString) | ||
| 256 | } | ||
| 257 | } | ||
| 258 | StringSetting.values().forEach { | ||
| 259 | if (!keySet.contains(it.key)) { | ||
| 260 | parser.put(header, it.key, it.valueAsString) | ||
| 261 | } | ||
| 262 | } | ||
| 240 | } | 263 | } |
| 241 | } | 264 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt index c92e2755c..2ff827c6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt | |||
| @@ -66,7 +66,11 @@ class AboutFragment : Fragment() { | |||
| 66 | true | 66 | true |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) } | 69 | binding.buttonContributors.setOnClickListener { |
| 70 | openLink( | ||
| 71 | getString(R.string.contributors_link) | ||
| 72 | ) | ||
| 73 | } | ||
| 70 | binding.buttonLicenses.setOnClickListener { | 74 | binding.buttonLicenses.setOnClickListener { |
| 71 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | 75 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
| 72 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) | 76 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) |
| @@ -101,7 +105,9 @@ class AboutFragment : Fragment() { | |||
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | private fun setInsets() = | 107 | private fun setInsets() = |
| 104 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 108 | ViewCompat.setOnApplyWindowInsetsListener( |
| 109 | binding.root | ||
| 110 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 105 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 111 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 106 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 112 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 107 | 113 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt index d8bbc1ce4..dbc16da4a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt | |||
| @@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() { | |||
| 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() | 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) } | 52 | binding.getEarlyAccessButton.setOnClickListener { |
| 53 | openLink( | ||
| 54 | getString(R.string.play_store_link) | ||
| 55 | ) | ||
| 56 | } | ||
| 53 | 57 | ||
| 54 | setInsets() | 58 | setInsets() |
| 55 | } | 59 | } |
| @@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() { | |||
| 60 | } | 64 | } |
| 61 | 65 | ||
| 62 | private fun setInsets() = | 66 | private fun setInsets() = |
| 63 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 67 | ViewCompat.setOnApplyWindowInsetsListener( |
| 68 | binding.root | ||
| 69 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 64 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 70 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 65 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 71 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 66 | 72 | ||
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 9523381cd..4643418c1 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 | |||
| @@ -7,30 +7,39 @@ import android.annotation.SuppressLint | |||
| 7 | import android.app.AlertDialog | 7 | import android.app.AlertDialog |
| 8 | import android.content.Context | 8 | import android.content.Context |
| 9 | import android.content.DialogInterface | 9 | import android.content.DialogInterface |
| 10 | import android.content.Intent | ||
| 10 | import android.content.SharedPreferences | 11 | import android.content.SharedPreferences |
| 11 | import android.content.pm.ActivityInfo | 12 | import android.content.pm.ActivityInfo |
| 12 | import android.content.res.Resources | 13 | import android.content.res.Configuration |
| 13 | import android.graphics.Color | 14 | import android.graphics.Color |
| 14 | import android.os.Bundle | 15 | import android.os.Bundle |
| 15 | import android.os.Handler | 16 | import android.os.Handler |
| 16 | import android.os.Looper | 17 | import android.os.Looper |
| 17 | import android.util.Rational | 18 | import android.util.Rational |
| 18 | import android.util.TypedValue | ||
| 19 | import android.view.* | 19 | import android.view.* |
| 20 | import android.widget.TextView | 20 | import android.widget.TextView |
| 21 | import androidx.activity.OnBackPressedCallback | 21 | import androidx.activity.OnBackPressedCallback |
| 22 | import androidx.activity.result.ActivityResultLauncher | ||
| 23 | import androidx.activity.result.contract.ActivityResultContracts | ||
| 22 | import androidx.appcompat.widget.PopupMenu | 24 | import androidx.appcompat.widget.PopupMenu |
| 23 | import androidx.core.content.res.ResourcesCompat | 25 | import androidx.core.content.res.ResourcesCompat |
| 24 | import androidx.core.graphics.Insets | 26 | import androidx.core.graphics.Insets |
| 25 | import androidx.core.view.ViewCompat | 27 | import androidx.core.view.ViewCompat |
| 26 | import androidx.core.view.WindowInsetsCompat | 28 | import androidx.core.view.WindowInsetsCompat |
| 27 | import androidx.core.view.updatePadding | 29 | import androidx.core.view.isVisible |
| 28 | import androidx.fragment.app.Fragment | 30 | import androidx.fragment.app.Fragment |
| 31 | import androidx.lifecycle.Lifecycle | ||
| 32 | import androidx.lifecycle.lifecycleScope | ||
| 33 | import androidx.lifecycle.repeatOnLifecycle | ||
| 34 | import androidx.navigation.fragment.navArgs | ||
| 29 | import androidx.preference.PreferenceManager | 35 | import androidx.preference.PreferenceManager |
| 30 | import androidx.window.layout.FoldingFeature | 36 | import androidx.window.layout.FoldingFeature |
| 37 | import androidx.window.layout.WindowInfoTracker | ||
| 31 | import androidx.window.layout.WindowLayoutInfo | 38 | import androidx.window.layout.WindowLayoutInfo |
| 32 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 33 | import com.google.android.material.slider.Slider | 40 | import com.google.android.material.slider.Slider |
| 41 | import kotlinx.coroutines.Dispatchers | ||
| 42 | import kotlinx.coroutines.launch | ||
| 34 | import org.yuzu.yuzu_emu.NativeLibrary | 43 | import org.yuzu.yuzu_emu.NativeLibrary |
| 35 | import org.yuzu.yuzu_emu.R | 44 | import org.yuzu.yuzu_emu.R |
| 36 | import org.yuzu.yuzu_emu.YuzuApplication | 45 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -41,9 +50,8 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting | |||
| 41 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 50 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 42 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | 51 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity |
| 43 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 52 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 44 | import org.yuzu.yuzu_emu.model.Game | 53 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 45 | import org.yuzu.yuzu_emu.utils.* | 54 | import org.yuzu.yuzu_emu.utils.* |
| 46 | import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable | ||
| 47 | 55 | ||
| 48 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 56 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
| 49 | private lateinit var preferences: SharedPreferences | 57 | private lateinit var preferences: SharedPreferences |
| @@ -54,13 +62,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 54 | private var _binding: FragmentEmulationBinding? = null | 62 | private var _binding: FragmentEmulationBinding? = null |
| 55 | private val binding get() = _binding!! | 63 | private val binding get() = _binding!! |
| 56 | 64 | ||
| 57 | private lateinit var game: Game | 65 | val args by navArgs<EmulationFragmentArgs>() |
| 66 | |||
| 67 | private var isInFoldableLayout = false | ||
| 68 | |||
| 69 | private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> | ||
| 58 | 70 | ||
| 59 | override fun onAttach(context: Context) { | 71 | override fun onAttach(context: Context) { |
| 60 | super.onAttach(context) | 72 | super.onAttach(context) |
| 61 | if (context is EmulationActivity) { | 73 | if (context is EmulationActivity) { |
| 62 | emulationActivity = context | 74 | emulationActivity = context |
| 63 | NativeLibrary.setEmulationActivity(context) | 75 | NativeLibrary.setEmulationActivity(context) |
| 76 | |||
| 77 | lifecycleScope.launch(Dispatchers.Main) { | ||
| 78 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 79 | WindowInfoTracker.getOrCreate(context) | ||
| 80 | .windowLayoutInfo(context) | ||
| 81 | .collect { updateFoldableLayout(context, it) } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | onReturnFromSettings = context.activityResultRegistry.register( | ||
| 86 | "SettingsResult", | ||
| 87 | ActivityResultContracts.StartActivityForResult() | ||
| 88 | ) { updateScreenLayout() } | ||
| 64 | } else { | 89 | } else { |
| 65 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 90 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 66 | } | 91 | } |
| @@ -75,8 +100,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 75 | // So this fragment doesn't restart on configuration changes; i.e. rotation. | 100 | // So this fragment doesn't restart on configuration changes; i.e. rotation. |
| 76 | retainInstance = true | 101 | retainInstance = true |
| 77 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 102 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 78 | game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!! | 103 | emulationState = EmulationState(args.game.path) |
| 79 | emulationState = EmulationState(game.path) | ||
| 80 | } | 104 | } |
| 81 | 105 | ||
| 82 | /** | 106 | /** |
| @@ -100,7 +124,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 100 | updateShowFpsOverlay() | 124 | updateShowFpsOverlay() |
| 101 | 125 | ||
| 102 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | 126 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = |
| 103 | game.title | 127 | args.game.title |
| 104 | binding.inGameMenu.setNavigationItemSelectedListener { | 128 | binding.inGameMenu.setNavigationItemSelectedListener { |
| 105 | when (it.itemId) { | 129 | when (it.itemId) { |
| 106 | R.id.menu_pause_emulation -> { | 130 | R.id.menu_pause_emulation -> { |
| @@ -125,7 +149,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 125 | } | 149 | } |
| 126 | 150 | ||
| 127 | R.id.menu_settings -> { | 151 | R.id.menu_settings -> { |
| 128 | SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") | 152 | SettingsActivity.launch( |
| 153 | requireContext(), | ||
| 154 | onReturnFromSettings, | ||
| 155 | SettingsFile.FILE_NAME_CONFIG, | ||
| 156 | "" | ||
| 157 | ) | ||
| 129 | true | 158 | true |
| 130 | } | 159 | } |
| 131 | 160 | ||
| @@ -150,9 +179,48 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 150 | requireActivity(), | 179 | requireActivity(), |
| 151 | object : OnBackPressedCallback(true) { | 180 | object : OnBackPressedCallback(true) { |
| 152 | override fun handleOnBackPressed() { | 181 | override fun handleOnBackPressed() { |
| 153 | if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() | 182 | if (binding.drawerLayout.isOpen) { |
| 183 | binding.drawerLayout.close() | ||
| 184 | } else { | ||
| 185 | binding.drawerLayout.open() | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | ) | ||
| 190 | |||
| 191 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { | ||
| 192 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 193 | WindowInfoTracker.getOrCreate(requireContext()) | ||
| 194 | .windowLayoutInfo(requireActivity()) | ||
| 195 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } | ||
| 196 | } | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | override fun onConfigurationChanged(newConfig: Configuration) { | ||
| 201 | super.onConfigurationChanged(newConfig) | ||
| 202 | if (emulationActivity?.isInPictureInPictureMode == true) { | ||
| 203 | if (binding.drawerLayout.isOpen) { | ||
| 204 | binding.drawerLayout.close() | ||
| 205 | } | ||
| 206 | if (EmulationMenuSettings.showOverlay) { | ||
| 207 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } | ||
| 208 | } | ||
| 209 | } else { | ||
| 210 | if (EmulationMenuSettings.showOverlay) { | ||
| 211 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } | ||
| 212 | } | ||
| 213 | if (!isInFoldableLayout) { | ||
| 214 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 215 | binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT | ||
| 216 | } else { | ||
| 217 | binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE | ||
| 154 | } | 218 | } |
| 155 | }) | 219 | } |
| 220 | if (!binding.surfaceInputOverlay.isInEditMode) { | ||
| 221 | refreshInputOverlay() | ||
| 222 | } | ||
| 223 | } | ||
| 156 | } | 224 | } |
| 157 | 225 | ||
| 158 | override fun onResume() { | 226 | override fun onResume() { |
| @@ -161,16 +229,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 161 | DirectoryInitialization.start(requireContext()) | 229 | DirectoryInitialization.start(requireContext()) |
| 162 | } | 230 | } |
| 163 | 231 | ||
| 164 | binding.surfaceEmulation.setAspectRatio( | 232 | updateScreenLayout() |
| 165 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 166 | 0 -> Rational(16, 9) | ||
| 167 | 1 -> Rational(4, 3) | ||
| 168 | 2 -> Rational(21, 9) | ||
| 169 | 3 -> Rational(16, 10) | ||
| 170 | 4 -> null // Stretch | ||
| 171 | else -> Rational(16, 9) | ||
| 172 | } | ||
| 173 | ) | ||
| 174 | 233 | ||
| 175 | emulationState.run(emulationActivity!!.isActivityRecreated) | 234 | emulationState.run(emulationActivity!!.isActivityRecreated) |
| 176 | } | 235 | } |
| @@ -231,31 +290,72 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 231 | } | 290 | } |
| 232 | } | 291 | } |
| 233 | 292 | ||
| 234 | private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt() | 293 | @SuppressLint("SourceLockedOrientationActivity") |
| 235 | 294 | private fun updateOrientation() { | |
| 236 | fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { | 295 | emulationActivity?.let { |
| 237 | val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | 296 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { |
| 238 | if (it.isSeparating) { | 297 | Settings.LayoutOption_MobileLandscape -> |
| 239 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 298 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 240 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | 299 | Settings.LayoutOption_MobilePortrait -> |
| 241 | binding.surfaceEmulation.layoutParams.height = it.bounds.top | 300 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT |
| 242 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | 301 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 243 | binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx | 302 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 244 | binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx) | ||
| 245 | } | ||
| 246 | } | 303 | } |
| 247 | it.isSeparating | 304 | } |
| 248 | } ?: false | 305 | } |
| 306 | |||
| 307 | private fun updateScreenLayout() { | ||
| 308 | binding.surfaceEmulation.setAspectRatio( | ||
| 309 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 310 | 0 -> Rational(16, 9) | ||
| 311 | 1 -> Rational(4, 3) | ||
| 312 | 2 -> Rational(21, 9) | ||
| 313 | 3 -> Rational(16, 10) | ||
| 314 | 4 -> null // Stretch | ||
| 315 | else -> Rational(16, 9) | ||
| 316 | } | ||
| 317 | ) | ||
| 318 | emulationActivity?.buildPictureInPictureParams() | ||
| 319 | updateOrientation() | ||
| 320 | } | ||
| 321 | |||
| 322 | private fun updateFoldableLayout( | ||
| 323 | emulationActivity: EmulationActivity, | ||
| 324 | newLayoutInfo: WindowLayoutInfo | ||
| 325 | ) { | ||
| 326 | val isFolding = | ||
| 327 | (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | ||
| 328 | if (it.isSeparating) { | ||
| 329 | emulationActivity.requestedOrientation = | ||
| 330 | ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 331 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | ||
| 332 | // Restrict emulation and overlays to the top of the screen | ||
| 333 | binding.emulationContainer.layoutParams.height = it.bounds.top | ||
| 334 | binding.overlayContainer.layoutParams.height = it.bounds.top | ||
| 335 | // Restrict input and menu drawer to the bottom of the screen | ||
| 336 | binding.inputContainer.layoutParams.height = it.bounds.bottom | ||
| 337 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | ||
| 338 | |||
| 339 | isInFoldableLayout = true | ||
| 340 | binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE | ||
| 341 | refreshInputOverlay() | ||
| 342 | } | ||
| 343 | } | ||
| 344 | it.isSeparating | ||
| 345 | } ?: false | ||
| 249 | if (!isFolding) { | 346 | if (!isFolding) { |
| 250 | binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 347 | binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 251 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 348 | binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 252 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 349 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 253 | binding.overlayContainer.updatePadding(0, 0, 0, 0) | 350 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 254 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE | 351 | isInFoldableLayout = false |
| 352 | updateOrientation() | ||
| 353 | onConfigurationChanged(resources.configuration) | ||
| 255 | } | 354 | } |
| 256 | binding.surfaceInputOverlay.requestLayout() | 355 | binding.emulationContainer.requestLayout() |
| 257 | binding.inGameMenu.requestLayout() | 356 | binding.inputContainer.requestLayout() |
| 258 | binding.overlayContainer.requestLayout() | 357 | binding.overlayContainer.requestLayout() |
| 358 | binding.inGameMenu.requestLayout() | ||
| 259 | } | 359 | } |
| 260 | 360 | ||
| 261 | override fun surfaceCreated(holder: SurfaceHolder) { | 361 | override fun surfaceCreated(holder: SurfaceHolder) { |
| @@ -385,7 +485,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 385 | popup.show() | 485 | popup.show() |
| 386 | } | 486 | } |
| 387 | 487 | ||
| 488 | @SuppressLint("SourceLockedOrientationActivity") | ||
| 388 | private fun startConfiguringControls() { | 489 | private fun startConfiguringControls() { |
| 490 | // Lock the current orientation to prevent editing inconsistencies | ||
| 491 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 492 | emulationActivity?.let { | ||
| 493 | it.requestedOrientation = | ||
| 494 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 495 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||
| 496 | } else { | ||
| 497 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
| 498 | } | ||
| 499 | } | ||
| 500 | } | ||
| 389 | binding.doneControlConfig.visibility = View.VISIBLE | 501 | binding.doneControlConfig.visibility = View.VISIBLE |
| 390 | binding.surfaceInputOverlay.setIsInEditMode(true) | 502 | binding.surfaceInputOverlay.setIsInEditMode(true) |
| 391 | } | 503 | } |
| @@ -393,6 +505,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 393 | private fun stopConfiguringControls() { | 505 | private fun stopConfiguringControls() { |
| 394 | binding.doneControlConfig.visibility = View.GONE | 506 | binding.doneControlConfig.visibility = View.GONE |
| 395 | binding.surfaceInputOverlay.setIsInEditMode(false) | 507 | binding.surfaceInputOverlay.setIsInEditMode(false) |
| 508 | // Unlock the orientation if it was locked for editing | ||
| 509 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 510 | emulationActivity?.let { | ||
| 511 | it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 512 | } | ||
| 513 | } | ||
| 396 | } | 514 | } |
| 397 | 515 | ||
| 398 | @SuppressLint("SetTextI18n") | 516 | @SuppressLint("SetTextI18n") |
| @@ -402,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 402 | inputScaleSlider.apply { | 520 | inputScaleSlider.apply { |
| 403 | valueTo = 150F | 521 | valueTo = 150F |
| 404 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() | 522 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() |
| 405 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 523 | addOnChangeListener( |
| 406 | inputScaleValue.text = "${value.toInt()}%" | 524 | Slider.OnChangeListener { _, value, _ -> |
| 407 | setControlScale(value.toInt()) | 525 | inputScaleValue.text = "${value.toInt()}%" |
| 408 | }) | 526 | setControlScale(value.toInt()) |
| 527 | } | ||
| 528 | ) | ||
| 409 | } | 529 | } |
| 410 | inputOpacitySlider.apply { | 530 | inputOpacitySlider.apply { |
| 411 | valueTo = 100F | 531 | valueTo = 100F |
| 412 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() | 532 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() |
| 413 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 533 | addOnChangeListener( |
| 414 | inputOpacityValue.text = "${value.toInt()}%" | 534 | Slider.OnChangeListener { _, value, _ -> |
| 415 | setControlOpacity(value.toInt()) | 535 | inputOpacityValue.text = "${value.toInt()}%" |
| 416 | }) | 536 | setControlOpacity(value.toInt()) |
| 537 | } | ||
| 538 | ) | ||
| 417 | } | 539 | } |
| 418 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" | 540 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" |
| 419 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" | 541 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" |
| @@ -445,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 445 | } | 567 | } |
| 446 | 568 | ||
| 447 | private fun setInsets() { | 569 | private fun setInsets() { |
| 448 | ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat -> | 570 | ViewCompat.setOnApplyWindowInsetsListener( |
| 571 | binding.inGameMenu | ||
| 572 | ) { v: View, windowInsets: WindowInsetsCompat -> | ||
| 449 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 573 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 450 | var left = 0 | 574 | var left = 0 |
| 451 | var right = 0 | 575 | var right = 0 |
| @@ -565,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 565 | state = State.PAUSED | 689 | state = State.PAUSED |
| 566 | } | 690 | } |
| 567 | 691 | ||
| 568 | State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") | 692 | State.PAUSED -> Log.warning( |
| 569 | else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") | 693 | "[EmulationFragment] Surface cleared while emulation paused." |
| 694 | ) | ||
| 695 | else -> Log.warning( | ||
| 696 | "[EmulationFragment] Surface cleared while emulation stopped." | ||
| 697 | ) | ||
| 570 | } | 698 | } |
| 571 | } | 699 | } |
| 572 | } | 700 | } |
| @@ -601,13 +729,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 601 | 729 | ||
| 602 | companion object { | 730 | companion object { |
| 603 | private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) | 731 | private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) |
| 604 | |||
| 605 | fun newInstance(game: Game): EmulationFragment { | ||
| 606 | val args = Bundle() | ||
| 607 | args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game) | ||
| 608 | val fragment = EmulationFragment() | ||
| 609 | fragment.arguments = args | ||
| 610 | return fragment | ||
| 611 | } | ||
| 612 | } | 732 | } |
| 613 | } | 733 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 536163eb6..5a36ffad4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -68,77 +68,109 @@ class HomeSettingsFragment : Fragment() { | |||
| 68 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 68 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 69 | mainActivity = requireActivity() as MainActivity | 69 | mainActivity = requireActivity() as MainActivity |
| 70 | 70 | ||
| 71 | val optionsList: MutableList<HomeSetting> = mutableListOf( | 71 | val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply { |
| 72 | HomeSetting( | 72 | add( |
| 73 | R.string.advanced_settings, | 73 | HomeSetting( |
| 74 | R.string.settings_description, | 74 | R.string.advanced_settings, |
| 75 | R.drawable.ic_settings | 75 | R.string.settings_description, |
| 76 | ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }, | 76 | R.drawable.ic_settings |
| 77 | HomeSetting( | 77 | ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } |
| 78 | R.string.open_user_folder, | 78 | ) |
| 79 | R.string.open_user_folder_description, | 79 | add( |
| 80 | R.drawable.ic_folder_open | 80 | HomeSetting( |
| 81 | ) { openFileManager() }, | 81 | R.string.open_user_folder, |
| 82 | HomeSetting( | 82 | R.string.open_user_folder_description, |
| 83 | R.string.preferences_theme, | 83 | R.drawable.ic_folder_open |
| 84 | R.string.theme_and_color_description, | 84 | ) { openFileManager() } |
| 85 | R.drawable.ic_palette | 85 | ) |
| 86 | ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }, | 86 | add( |
| 87 | HomeSetting( | 87 | HomeSetting( |
| 88 | R.string.install_gpu_driver, | 88 | R.string.preferences_theme, |
| 89 | R.string.install_gpu_driver_description, | 89 | R.string.theme_and_color_description, |
| 90 | R.drawable.ic_exit | 90 | R.drawable.ic_palette |
| 91 | ) { driverInstaller() }, | 91 | ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } |
| 92 | HomeSetting( | 92 | ) |
| 93 | R.string.install_amiibo_keys, | 93 | |
| 94 | R.string.install_amiibo_keys_description, | 94 | if (GpuDriverHelper.supportsCustomDriverLoading()) { |
| 95 | R.drawable.ic_nfc | 95 | add( |
| 96 | ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }, | 96 | HomeSetting( |
| 97 | HomeSetting( | 97 | R.string.install_gpu_driver, |
| 98 | R.string.install_game_content, | 98 | R.string.install_gpu_driver_description, |
| 99 | R.string.install_game_content_description, | 99 | R.drawable.ic_exit |
| 100 | R.drawable.ic_system_update_alt | 100 | ) { driverInstaller() } |
| 101 | ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }, | ||
| 102 | HomeSetting( | ||
| 103 | R.string.select_games_folder, | ||
| 104 | R.string.select_games_folder_description, | ||
| 105 | R.drawable.ic_add | ||
| 106 | ) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | ||
| 107 | HomeSetting( | ||
| 108 | R.string.manage_save_data, | ||
| 109 | R.string.import_export_saves_description, | ||
| 110 | R.drawable.ic_save | ||
| 111 | ) { | ||
| 112 | ImportExportSavesFragment().show( | ||
| 113 | parentFragmentManager, | ||
| 114 | ImportExportSavesFragment.TAG | ||
| 115 | ) | 101 | ) |
| 116 | }, | ||
| 117 | HomeSetting( | ||
| 118 | R.string.install_prod_keys, | ||
| 119 | R.string.install_prod_keys_description, | ||
| 120 | R.drawable.ic_unlock | ||
| 121 | ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }, | ||
| 122 | HomeSetting( | ||
| 123 | R.string.install_firmware, | ||
| 124 | R.string.install_firmware_description, | ||
| 125 | R.drawable.ic_firmware | ||
| 126 | ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }, | ||
| 127 | HomeSetting( | ||
| 128 | R.string.share_log, | ||
| 129 | R.string.share_log_description, | ||
| 130 | R.drawable.ic_log | ||
| 131 | ) { shareLog() }, | ||
| 132 | HomeSetting( | ||
| 133 | R.string.about, | ||
| 134 | R.string.about_description, | ||
| 135 | R.drawable.ic_info_outline | ||
| 136 | ) { | ||
| 137 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 138 | parentFragmentManager.primaryNavigationFragment?.findNavController() | ||
| 139 | ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment) | ||
| 140 | } | 102 | } |
| 141 | ) | 103 | |
| 104 | add( | ||
| 105 | HomeSetting( | ||
| 106 | R.string.install_amiibo_keys, | ||
| 107 | R.string.install_amiibo_keys_description, | ||
| 108 | R.drawable.ic_nfc | ||
| 109 | ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) } | ||
| 110 | ) | ||
| 111 | add( | ||
| 112 | HomeSetting( | ||
| 113 | R.string.install_game_content, | ||
| 114 | R.string.install_game_content_description, | ||
| 115 | R.drawable.ic_system_update_alt | ||
| 116 | ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) } | ||
| 117 | ) | ||
| 118 | add( | ||
| 119 | HomeSetting( | ||
| 120 | R.string.select_games_folder, | ||
| 121 | R.string.select_games_folder_description, | ||
| 122 | R.drawable.ic_add | ||
| 123 | ) { | ||
| 124 | mainActivity.getGamesDirectory.launch( | ||
| 125 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | ||
| 126 | ) | ||
| 127 | } | ||
| 128 | ) | ||
| 129 | add( | ||
| 130 | HomeSetting( | ||
| 131 | R.string.manage_save_data, | ||
| 132 | R.string.import_export_saves_description, | ||
| 133 | R.drawable.ic_save | ||
| 134 | ) { | ||
| 135 | ImportExportSavesFragment().show( | ||
| 136 | parentFragmentManager, | ||
| 137 | ImportExportSavesFragment.TAG | ||
| 138 | ) | ||
| 139 | } | ||
| 140 | ) | ||
| 141 | add( | ||
| 142 | HomeSetting( | ||
| 143 | R.string.install_prod_keys, | ||
| 144 | R.string.install_prod_keys_description, | ||
| 145 | R.drawable.ic_unlock | ||
| 146 | ) { mainActivity.getProdKey.launch(arrayOf("*/*")) } | ||
| 147 | ) | ||
| 148 | add( | ||
| 149 | HomeSetting( | ||
| 150 | R.string.install_firmware, | ||
| 151 | R.string.install_firmware_description, | ||
| 152 | R.drawable.ic_firmware | ||
| 153 | ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) } | ||
| 154 | ) | ||
| 155 | add( | ||
| 156 | HomeSetting( | ||
| 157 | R.string.share_log, | ||
| 158 | R.string.share_log_description, | ||
| 159 | R.drawable.ic_log | ||
| 160 | ) { shareLog() } | ||
| 161 | ) | ||
| 162 | add( | ||
| 163 | HomeSetting( | ||
| 164 | R.string.about, | ||
| 165 | R.string.about_description, | ||
| 166 | R.drawable.ic_info_outline | ||
| 167 | ) { | ||
| 168 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 169 | parentFragmentManager.primaryNavigationFragment?.findNavController() | ||
| 170 | ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment) | ||
| 171 | } | ||
| 172 | ) | ||
| 173 | } | ||
| 142 | 174 | ||
| 143 | if (!BuildConfig.PREMIUM) { | 175 | if (!BuildConfig.PREMIUM) { |
| 144 | optionsList.add( | 176 | optionsList.add( |
| @@ -225,7 +257,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 225 | val intent = Intent(action) | 257 | val intent = Intent(action) |
| 226 | intent.addCategory(Intent.CATEGORY_DEFAULT) | 258 | intent.addCategory(Intent.CATEGORY_DEFAULT) |
| 227 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) | 259 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) |
| 228 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | 260 | intent.addFlags( |
| 261 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or | ||
| 262 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or | ||
| 263 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | ||
| 264 | ) | ||
| 229 | return intent | 265 | return intent |
| 230 | } | 266 | } |
| 231 | 267 | ||
| @@ -307,7 +343,9 @@ class HomeSettingsFragment : Fragment() { | |||
| 307 | } | 343 | } |
| 308 | 344 | ||
| 309 | private fun setInsets() = | 345 | private fun setInsets() = |
| 310 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 346 | ViewCompat.setOnApplyWindowInsetsListener( |
| 347 | binding.root | ||
| 348 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 311 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 349 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 312 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 350 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 313 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) | 351 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt index 36e63bb9e..e1495ee8c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt | |||
| @@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 15 | import androidx.documentfile.provider.DocumentFile | 15 | import androidx.documentfile.provider.DocumentFile |
| 16 | import androidx.fragment.app.DialogFragment | 16 | import androidx.fragment.app.DialogFragment |
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 18 | import java.io.BufferedOutputStream | ||
| 19 | import java.io.File | ||
| 20 | import java.io.FileOutputStream | ||
| 21 | import java.io.FilenameFilter | ||
| 22 | import java.time.LocalDateTime | ||
| 23 | import java.time.format.DateTimeFormatter | ||
| 24 | import java.util.zip.ZipEntry | ||
| 25 | import java.util.zip.ZipOutputStream | ||
| 18 | import kotlinx.coroutines.CoroutineScope | 26 | import kotlinx.coroutines.CoroutineScope |
| 19 | import kotlinx.coroutines.Dispatchers | 27 | import kotlinx.coroutines.Dispatchers |
| 20 | import kotlinx.coroutines.launch | 28 | import kotlinx.coroutines.launch |
| @@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication | |||
| 24 | import org.yuzu.yuzu_emu.features.DocumentProvider | 32 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 25 | import org.yuzu.yuzu_emu.getPublicFilesDir | 33 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 26 | import org.yuzu.yuzu_emu.utils.FileUtil | 34 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 27 | import java.io.BufferedOutputStream | ||
| 28 | import java.io.File | ||
| 29 | import java.io.FileOutputStream | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.time.LocalDateTime | ||
| 32 | import java.time.format.DateTimeFormatter | ||
| 33 | import java.util.zip.ZipEntry | ||
| 34 | import java.util.zip.ZipOutputStream | ||
| 35 | 35 | ||
| 36 | class ImportExportSavesFragment : DialogFragment() { | 36 | class ImportExportSavesFragment : DialogFragment() { |
| 37 | private val context = YuzuApplication.appContext | 37 | private val context = YuzuApplication.appContext |
| @@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 98 | val outputZipFile = File( | 98 | val outputZipFile = File( |
| 99 | tempFolder, | 99 | tempFolder, |
| 100 | "yuzu saves - ${ | 100 | "yuzu saves - ${ |
| 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) |
| 102 | }.zip" | 102 | }.zip" |
| 103 | ) | 103 | ) |
| 104 | outputZipFile.createNewFile() | 104 | outputZipFile.createNewFile() |
| @@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 106 | saveFolder.walkTopDown().forEach { file -> | 106 | saveFolder.walkTopDown().forEach { file -> |
| 107 | val zipFileName = | 107 | val zipFileName = |
| 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") | 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") |
| 109 | if (zipFileName == "") | 109 | if (zipFileName == "") { |
| 110 | return@forEach | 110 | return@forEach |
| 111 | } | ||
| 111 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") | 112 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") |
| 112 | zos.putNextEntry(entry) | 113 | zos.putNextEntry(entry) |
| 113 | if (file.isFile) | 114 | if (file.isFile) { |
| 114 | file.inputStream().use { fis -> fis.copyTo(zos) } | 115 | file.inputStream().use { fis -> fis.copyTo(zos) } |
| 116 | } | ||
| 115 | } | 117 | } |
| 116 | } | 118 | } |
| 117 | lastZipCreated = outputZipFile | 119 | lastZipCreated = outputZipFile |
| @@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 137 | 139 | ||
| 138 | withContext(Dispatchers.Main) { | 140 | withContext(Dispatchers.Main) { |
| 139 | val file = DocumentFile.fromSingleUri( | 141 | val file = DocumentFile.fromSingleUri( |
| 140 | context, DocumentsContract.buildDocumentUri( | 142 | context, |
| 143 | DocumentsContract.buildDocumentUri( | ||
| 141 | DocumentProvider.AUTHORITY, | 144 | DocumentProvider.AUTHORITY, |
| 142 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" | 145 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" |
| 143 | ) | 146 | ) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index c7880d8cc..739b26f99 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt | |||
| @@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder | |||
| 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 15 | import org.yuzu.yuzu_emu.model.TaskViewModel | 15 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 16 | 16 | ||
| 17 | |||
| 18 | class IndeterminateProgressDialogFragment : DialogFragment() { | 17 | class IndeterminateProgressDialogFragment : DialogFragment() { |
| 19 | private val taskViewModel: TaskViewModel by activityViewModels() | 18 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 20 | 19 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt index 59141e823..b6e9129f7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt | |||
| @@ -113,7 +113,9 @@ class LicensesFragment : Fragment() { | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | private fun setInsets() = | 115 | private fun setInsets() = |
| 116 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 116 | ViewCompat.setOnApplyWindowInsetsListener( |
| 117 | binding.root | ||
| 118 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 117 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 119 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 118 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 120 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 119 | 121 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index adbe3696b..dd6c895fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt | |||
| @@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels | |||
| 20 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
| 21 | import info.debatty.java.stringsimilarity.Jaccard | 21 | import info.debatty.java.stringsimilarity.Jaccard |
| 22 | import info.debatty.java.stringsimilarity.JaroWinkler | 22 | import info.debatty.java.stringsimilarity.JaroWinkler |
| 23 | import java.util.Locale | ||
| 23 | import org.yuzu.yuzu_emu.R | 24 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.YuzuApplication | 25 | import org.yuzu.yuzu_emu.YuzuApplication |
| 25 | import org.yuzu.yuzu_emu.adapters.GameAdapter | 26 | import org.yuzu.yuzu_emu.adapters.GameAdapter |
| @@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 29 | import org.yuzu.yuzu_emu.model.GamesViewModel | 30 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 30 | import org.yuzu.yuzu_emu.model.HomeViewModel | 31 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 31 | import org.yuzu.yuzu_emu.utils.FileUtil | 32 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 32 | import org.yuzu.yuzu_emu.utils.Log | ||
| 33 | import java.util.Locale | ||
| 34 | 33 | ||
| 35 | class SearchFragment : Fragment() { | 34 | class SearchFragment : Fragment() { |
| 36 | private var _binding: FragmentSearchBinding? = null | 35 | private var _binding: FragmentSearchBinding? = null |
| @@ -130,15 +129,15 @@ class SearchFragment : Fragment() { | |||
| 130 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } | 129 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } |
| 131 | 130 | ||
| 132 | R.id.chip_retail -> baseList.filter { | 131 | R.id.chip_retail -> baseList.filter { |
| 133 | FileUtil.hasExtension(it.path, "xci") | 132 | FileUtil.hasExtension(it.path, "xci") || |
| 134 | || FileUtil.hasExtension(it.path, "nsp") | 133 | FileUtil.hasExtension(it.path, "nsp") |
| 135 | } | 134 | } |
| 136 | 135 | ||
| 137 | else -> baseList | 136 | else -> baseList |
| 138 | } | 137 | } |
| 139 | 138 | ||
| 140 | if (binding.searchText.text.toString().isEmpty() | 139 | if (binding.searchText.text.toString().isEmpty() && |
| 141 | && binding.chipGroup.checkedChipId != View.NO_ID | 140 | binding.chipGroup.checkedChipId != View.NO_ID |
| 142 | ) { | 141 | ) { |
| 143 | gamesViewModel.setSearchedGames(filteredList) | 142 | gamesViewModel.setSearchedGames(filteredList) |
| 144 | return | 143 | return |
| @@ -173,14 +172,16 @@ class SearchFragment : Fragment() { | |||
| 173 | private fun focusSearch() { | 172 | private fun focusSearch() { |
| 174 | if (_binding != null) { | 173 | if (_binding != null) { |
| 175 | binding.searchText.requestFocus() | 174 | binding.searchText.requestFocus() |
| 176 | val imm = | 175 | val imm = requireActivity() |
| 177 | requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | 176 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? |
| 178 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | 177 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) |
| 179 | } | 178 | } |
| 180 | } | 179 | } |
| 181 | 180 | ||
| 182 | private fun setInsets() = | 181 | private fun setInsets() = |
| 183 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 182 | ViewCompat.setOnApplyWindowInsetsListener( |
| 183 | binding.root | ||
| 184 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 184 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 185 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 185 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 186 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 186 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | 187 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 258773380..6c4ddaf6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt | |||
| @@ -25,6 +25,7 @@ import androidx.navigation.findNavController | |||
| 25 | import androidx.preference.PreferenceManager | 25 | import androidx.preference.PreferenceManager |
| 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback |
| 27 | import com.google.android.material.transition.MaterialFadeThrough | 27 | import com.google.android.material.transition.MaterialFadeThrough |
| 28 | import java.io.File | ||
| 28 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 29 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 30 | import org.yuzu.yuzu_emu.adapters.SetupAdapter | 31 | import org.yuzu.yuzu_emu.adapters.SetupAdapter |
| @@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage | |||
| 35 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 36 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 36 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 37 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 37 | import org.yuzu.yuzu_emu.utils.GameHelper | 38 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 38 | import java.io.File | ||
| 39 | 39 | ||
| 40 | class SetupFragment : Fragment() { | 40 | class SetupFragment : Fragment() { |
| 41 | private var _binding: FragmentSetupBinding? = null | 41 | private var _binding: FragmentSetupBinding? = null |
| @@ -82,7 +82,8 @@ class SetupFragment : Fragment() { | |||
| 82 | requireActivity().finish() | 82 | requireActivity().finish() |
| 83 | } | 83 | } |
| 84 | } | 84 | } |
| 85 | }) | 85 | } |
| 86 | ) | ||
| 86 | 87 | ||
| 87 | requireActivity().window.navigationBarColor = | 88 | requireActivity().window.navigationBarColor = |
| 88 | ContextCompat.getColor(requireContext(), android.R.color.transparent) | 89 | ContextCompat.getColor(requireContext(), android.R.color.transparent) |
| @@ -148,14 +149,20 @@ class SetupFragment : Fragment() { | |||
| 148 | R.drawable.ic_add, | 149 | R.drawable.ic_add, |
| 149 | true, | 150 | true, |
| 150 | R.string.add_games, | 151 | R.string.add_games, |
| 151 | { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | 152 | { |
| 153 | mainActivity.getGamesDirectory.launch( | ||
| 154 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | ||
| 155 | ) | ||
| 156 | }, | ||
| 152 | true, | 157 | true, |
| 153 | R.string.add_games_warning, | 158 | R.string.add_games_warning, |
| 154 | R.string.add_games_warning_description, | 159 | R.string.add_games_warning_description, |
| 155 | R.string.add_games_warning_help, | 160 | R.string.add_games_warning_help, |
| 156 | { | 161 | { |
| 157 | val preferences = | 162 | val preferences = |
| 158 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 163 | PreferenceManager.getDefaultSharedPreferences( |
| 164 | YuzuApplication.appContext | ||
| 165 | ) | ||
| 159 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() | 166 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() |
| 160 | } | 167 | } |
| 161 | ) | 168 | ) |
| @@ -260,7 +267,9 @@ class SetupFragment : Fragment() { | |||
| 260 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) | 267 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| 261 | private val permissionLauncher = | 268 | private val permissionLauncher = |
| 262 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { | 269 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { |
| 263 | if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { | 270 | if (!it && |
| 271 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) | ||
| 272 | ) { | ||
| 264 | PermissionDeniedDialogFragment().show( | 273 | PermissionDeniedDialogFragment().show( |
| 265 | childFragmentManager, | 274 | childFragmentManager, |
| 266 | PermissionDeniedDialogFragment.TAG | 275 | PermissionDeniedDialogFragment.TAG |
| @@ -315,7 +324,9 @@ class SetupFragment : Fragment() { | |||
| 315 | } | 324 | } |
| 316 | 325 | ||
| 317 | private fun setInsets() = | 326 | private fun setInsets() = |
| 318 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 327 | ViewCompat.setOnApplyWindowInsetsListener( |
| 328 | binding.root | ||
| 329 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 319 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 330 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 320 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 331 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 321 | view.setPadding( | 332 | view.setPadding( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt index be5e4c86c..bdd6ea628 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt | |||
| @@ -44,7 +44,9 @@ class AutofitGridLayoutManager( | |||
| 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { | 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { |
| 45 | val width = width | 45 | val width = width |
| 46 | val height = height | 46 | val height = height |
| 47 | if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { | 47 | if (columnWidth > 0 && width > 0 && height > 0 && |
| 48 | (isColumnWidthChanged || lastWidth != width || lastHeight != height) | ||
| 49 | ) { | ||
| 48 | val totalSpace: Int = if (orientation == VERTICAL) { | 50 | val totalSpace: Int = if (orientation == VERTICAL) { |
| 49 | width - paddingRight - paddingLeft | 51 | width - paddingRight - paddingLeft |
| 50 | } else { | 52 | } else { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 35d8000c5..6a048e39f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt | |||
| @@ -4,9 +4,9 @@ | |||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import android.os.Parcelable | 6 | import android.os.Parcelable |
| 7 | import java.util.HashSet | ||
| 7 | import kotlinx.parcelize.Parcelize | 8 | import kotlinx.parcelize.Parcelize |
| 8 | import kotlinx.serialization.Serializable | 9 | import kotlinx.serialization.Serializable |
| 9 | import java.util.HashSet | ||
| 10 | 10 | ||
| 11 | @Parcelize | 11 | @Parcelize |
| 12 | @Serializable | 12 | @Serializable |
| @@ -23,8 +23,9 @@ class Game( | |||
| 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" | 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" |
| 24 | 24 | ||
| 25 | override fun equals(other: Any?): Boolean { | 25 | override fun equals(other: Any?): Boolean { |
| 26 | if (other !is Game) | 26 | if (other !is Game) { |
| 27 | return false | 27 | return false |
| 28 | } | ||
| 28 | 29 | ||
| 29 | return hashCode() == other.hashCode() | 30 | return hashCode() == other.hashCode() |
| 30 | } | 31 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index d9b301210..1fe42f922 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt | |||
| @@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData | |||
| 10 | import androidx.lifecycle.ViewModel | 10 | import androidx.lifecycle.ViewModel |
| 11 | import androidx.lifecycle.viewModelScope | 11 | import androidx.lifecycle.viewModelScope |
| 12 | import androidx.preference.PreferenceManager | 12 | import androidx.preference.PreferenceManager |
| 13 | import java.util.Locale | ||
| 13 | import kotlinx.coroutines.Dispatchers | 14 | import kotlinx.coroutines.Dispatchers |
| 14 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| 15 | import kotlinx.coroutines.withContext | 16 | import kotlinx.coroutines.withContext |
| @@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json | |||
| 20 | import org.yuzu.yuzu_emu.NativeLibrary | 21 | import org.yuzu.yuzu_emu.NativeLibrary |
| 21 | import org.yuzu.yuzu_emu.YuzuApplication | 22 | import org.yuzu.yuzu_emu.YuzuApplication |
| 22 | import org.yuzu.yuzu_emu.utils.GameHelper | 23 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 23 | import java.util.Locale | ||
| 24 | 24 | ||
| 25 | @OptIn(ExperimentalSerializationApi::class) | 25 | @OptIn(ExperimentalSerializationApi::class) |
| 26 | class GamesViewModel : ViewModel() { | 26 | class GamesViewModel : ViewModel() { |
| @@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | fun reloadGames(directoryChanged: Boolean) { | 101 | fun reloadGames(directoryChanged: Boolean) { |
| 102 | if (isReloading.value == true) | 102 | if (isReloading.value == true) { |
| 103 | return | 103 | return |
| 104 | } | ||
| 104 | _isReloading.postValue(true) | 105 | _isReloading.postValue(true) |
| 105 | 106 | ||
| 106 | viewModelScope.launch { | 107 | viewModelScope.launch { |
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 aa424c768..6251ec783 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 | |||
| @@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay | |||
| 6 | import android.app.Activity | 6 | import android.app.Activity |
| 7 | import android.content.Context | 7 | import android.content.Context |
| 8 | import android.content.SharedPreferences | 8 | import android.content.SharedPreferences |
| 9 | import android.content.res.Configuration | ||
| 10 | import android.graphics.Bitmap | 9 | import android.graphics.Bitmap |
| 11 | import android.graphics.Canvas | 10 | import android.graphics.Canvas |
| 12 | import android.graphics.Point | 11 | import android.graphics.Point |
| @@ -24,6 +23,8 @@ import android.view.WindowInsets | |||
| 24 | import androidx.core.content.ContextCompat | 23 | import androidx.core.content.ContextCompat |
| 25 | import androidx.preference.PreferenceManager | 24 | import androidx.preference.PreferenceManager |
| 26 | import androidx.window.layout.WindowMetricsCalculator | 25 | import androidx.window.layout.WindowMetricsCalculator |
| 26 | import kotlin.math.max | ||
| 27 | import kotlin.math.min | ||
| 27 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 28 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType | 29 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType |
| 29 | import org.yuzu.yuzu_emu.NativeLibrary.StickType | 30 | import org.yuzu.yuzu_emu.NativeLibrary.StickType |
| @@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R | |||
| 31 | import org.yuzu.yuzu_emu.YuzuApplication | 32 | import org.yuzu.yuzu_emu.YuzuApplication |
| 32 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 33 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 33 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | 34 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings |
| 34 | import kotlin.math.max | ||
| 35 | import kotlin.math.min | ||
| 36 | 35 | ||
| 37 | /** | 36 | /** |
| 38 | * Draws the interactive input overlay on top of the | 37 | * Draws the interactive input overlay on top of the |
| 39 | * [SurfaceView] that is rendering emulation. | 38 | * [SurfaceView] that is rendering emulation. |
| 40 | */ | 39 | */ |
| 41 | class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), | 40 | class InputOverlay(context: Context, attrs: AttributeSet?) : |
| 41 | SurfaceView(context, attrs), | ||
| 42 | OnTouchListener { | 42 | OnTouchListener { |
| 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() | 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() |
| 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() | 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() |
| @@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 51 | 51 | ||
| 52 | private lateinit var windowInsets: WindowInsets | 52 | private lateinit var windowInsets: WindowInsets |
| 53 | 53 | ||
| 54 | var orientation = LANDSCAPE | ||
| 55 | |||
| 54 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { | 56 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { |
| 55 | super.onLayout(changed, left, top, right, bottom) | 57 | super.onLayout(changed, left, top, right, bottom) |
| 56 | 58 | ||
| 57 | windowInsets = rootWindowInsets | 59 | windowInsets = rootWindowInsets |
| 58 | 60 | ||
| 59 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | 61 | if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { |
| 60 | defaultOverlay() | 62 | defaultOverlay() |
| 61 | } | 63 | } |
| 62 | 64 | ||
| @@ -93,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 93 | 95 | ||
| 94 | var shouldUpdateView = false | 96 | var shouldUpdateView = false |
| 95 | val playerIndex = | 97 | val playerIndex = |
| 96 | if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 98 | if (NativeLibrary.isHandheldOnly()) { |
| 99 | NativeLibrary.ConsoleDevice | ||
| 100 | } else { | ||
| 101 | NativeLibrary.Player1Device | ||
| 102 | } | ||
| 97 | 103 | ||
| 98 | for (button in overlayButtons) { | 104 | for (button in overlayButtons) { |
| 99 | if (!button.updateStatus(event)) { | 105 | if (!button.updateStatus(event)) { |
| @@ -156,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 156 | shouldUpdateView = true | 162 | shouldUpdateView = true |
| 157 | } | 163 | } |
| 158 | 164 | ||
| 159 | if (shouldUpdateView) | 165 | if (shouldUpdateView) { |
| 160 | invalidate() | 166 | invalidate() |
| 167 | } | ||
| 161 | 168 | ||
| 162 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { | 169 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { |
| 163 | return true | 170 | return true |
| @@ -233,10 +240,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 233 | val fingerPositionX = event.getX(pointerIndex).toInt() | 240 | val fingerPositionX = event.getX(pointerIndex).toInt() |
| 234 | val fingerPositionY = event.getY(pointerIndex).toInt() | 241 | val fingerPositionY = event.getY(pointerIndex).toInt() |
| 235 | 242 | ||
| 236 | // TODO: Provide support for portrait layout | ||
| 237 | //val orientation = | ||
| 238 | // if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 239 | |||
| 240 | for (button in overlayButtons) { | 243 | for (button in overlayButtons) { |
| 241 | // Determine the button state to apply based on the MotionEvent action flag. | 244 | // Determine the button state to apply based on the MotionEvent action flag. |
| 242 | when (event.action and MotionEvent.ACTION_MASK) { | 245 | when (event.action and MotionEvent.ACTION_MASK) { |
| @@ -245,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 245 | // If no button is being moved now, remember the currently touched button to move. | 248 | // If no button is being moved now, remember the currently touched button to move. |
| 246 | if (buttonBeingConfigured == null && | 249 | if (buttonBeingConfigured == null && |
| 247 | button.bounds.contains( | 250 | button.bounds.contains( |
| 248 | fingerPositionX, | 251 | fingerPositionX, |
| 249 | fingerPositionY | 252 | fingerPositionY |
| 250 | ) | 253 | ) |
| 251 | ) { | 254 | ) { |
| 252 | buttonBeingConfigured = button | 255 | buttonBeingConfigured = button |
| 253 | buttonBeingConfigured!!.onConfigureTouch(event) | 256 | buttonBeingConfigured!!.onConfigureTouch(event) |
| @@ -266,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 266 | buttonBeingConfigured!!.buttonId, | 269 | buttonBeingConfigured!!.buttonId, |
| 267 | buttonBeingConfigured!!.bounds.centerX(), | 270 | buttonBeingConfigured!!.bounds.centerX(), |
| 268 | buttonBeingConfigured!!.bounds.centerY(), | 271 | buttonBeingConfigured!!.bounds.centerY(), |
| 269 | "" | 272 | orientation |
| 270 | ) | 273 | ) |
| 271 | buttonBeingConfigured = null | 274 | buttonBeingConfigured = null |
| 272 | } | 275 | } |
| @@ -299,7 +302,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 299 | dpadBeingConfigured!!.upId, | 302 | dpadBeingConfigured!!.upId, |
| 300 | dpadBeingConfigured!!.bounds.centerX(), | 303 | dpadBeingConfigured!!.bounds.centerX(), |
| 301 | dpadBeingConfigured!!.bounds.centerY(), | 304 | dpadBeingConfigured!!.bounds.centerY(), |
| 302 | "" | 305 | orientation |
| 303 | ) | 306 | ) |
| 304 | dpadBeingConfigured = null | 307 | dpadBeingConfigured = null |
| 305 | } | 308 | } |
| @@ -311,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 311 | MotionEvent.ACTION_DOWN, | 314 | MotionEvent.ACTION_DOWN, |
| 312 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && | 315 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && |
| 313 | joystick.bounds.contains( | 316 | joystick.bounds.contains( |
| 314 | fingerPositionX, | 317 | fingerPositionX, |
| 315 | fingerPositionY | 318 | fingerPositionY |
| 316 | ) | 319 | ) |
| 317 | ) { | 320 | ) { |
| 318 | joystickBeingConfigured = joystick | 321 | joystickBeingConfigured = joystick |
| 319 | joystickBeingConfigured!!.onConfigureTouch(event) | 322 | joystickBeingConfigured!!.onConfigureTouch(event) |
| @@ -330,7 +333,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 330 | joystickBeingConfigured!!.buttonId, | 333 | joystickBeingConfigured!!.buttonId, |
| 331 | joystickBeingConfigured!!.bounds.centerX(), | 334 | joystickBeingConfigured!!.bounds.centerX(), |
| 332 | joystickBeingConfigured!!.bounds.centerY(), | 335 | joystickBeingConfigured!!.bounds.centerY(), |
| 333 | "" | 336 | orientation |
| 334 | ) | 337 | ) |
| 335 | joystickBeingConfigured = null | 338 | joystickBeingConfigured = null |
| 336 | } | 339 | } |
| @@ -533,8 +536,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 533 | overlayButtons.clear() | 536 | overlayButtons.clear() |
| 534 | overlayDpads.clear() | 537 | overlayDpads.clear() |
| 535 | overlayJoysticks.clear() | 538 | overlayJoysticks.clear() |
| 536 | val orientation = | ||
| 537 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 538 | 539 | ||
| 539 | // Add all the enabled overlay items back to the HashSet. | 540 | // Add all the enabled overlay items back to the HashSet. |
| 540 | if (EmulationMenuSettings.showOverlay) { | 541 | if (EmulationMenuSettings.showOverlay) { |
| @@ -548,8 +549,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 548 | val min = windowSize.first | 549 | val min = windowSize.first |
| 549 | val max = windowSize.second | 550 | val max = windowSize.second |
| 550 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 551 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| 551 | .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x) | 552 | .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) |
| 552 | .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y) | 553 | .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) |
| 553 | .apply() | 554 | .apply() |
| 554 | } | 555 | } |
| 555 | 556 | ||
| @@ -558,145 +559,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 558 | } | 559 | } |
| 559 | 560 | ||
| 560 | private fun defaultOverlay() { | 561 | private fun defaultOverlay() { |
| 561 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | 562 | if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { |
| 562 | defaultOverlayLandscape() | 563 | defaultOverlayByLayout(orientation) |
| 563 | } | 564 | } |
| 564 | 565 | ||
| 565 | resetButtonPlacement() | 566 | resetButtonPlacement() |
| 566 | preferences.edit() | 567 | preferences.edit() |
| 567 | .putBoolean(Settings.PREF_OVERLAY_INIT, true) | 568 | .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) |
| 568 | .apply() | 569 | .apply() |
| 569 | } | 570 | } |
| 570 | 571 | ||
| 571 | fun resetButtonPlacement() { | 572 | fun resetButtonPlacement() { |
| 572 | defaultOverlayLandscape() | 573 | defaultOverlayByLayout(orientation) |
| 573 | refreshControls() | 574 | refreshControls() |
| 574 | } | 575 | } |
| 575 | 576 | ||
| 576 | private fun defaultOverlayLandscape() { | 577 | private val landscapeResources = arrayOf( |
| 578 | R.integer.SWITCH_BUTTON_A_X, | ||
| 579 | R.integer.SWITCH_BUTTON_A_Y, | ||
| 580 | R.integer.SWITCH_BUTTON_B_X, | ||
| 581 | R.integer.SWITCH_BUTTON_B_Y, | ||
| 582 | R.integer.SWITCH_BUTTON_X_X, | ||
| 583 | R.integer.SWITCH_BUTTON_X_Y, | ||
| 584 | R.integer.SWITCH_BUTTON_Y_X, | ||
| 585 | R.integer.SWITCH_BUTTON_Y_Y, | ||
| 586 | R.integer.SWITCH_TRIGGER_ZL_X, | ||
| 587 | R.integer.SWITCH_TRIGGER_ZL_Y, | ||
| 588 | R.integer.SWITCH_TRIGGER_ZR_X, | ||
| 589 | R.integer.SWITCH_TRIGGER_ZR_Y, | ||
| 590 | R.integer.SWITCH_BUTTON_DPAD_X, | ||
| 591 | R.integer.SWITCH_BUTTON_DPAD_Y, | ||
| 592 | R.integer.SWITCH_TRIGGER_L_X, | ||
| 593 | R.integer.SWITCH_TRIGGER_L_Y, | ||
| 594 | R.integer.SWITCH_TRIGGER_R_X, | ||
| 595 | R.integer.SWITCH_TRIGGER_R_Y, | ||
| 596 | R.integer.SWITCH_BUTTON_PLUS_X, | ||
| 597 | R.integer.SWITCH_BUTTON_PLUS_Y, | ||
| 598 | R.integer.SWITCH_BUTTON_MINUS_X, | ||
| 599 | R.integer.SWITCH_BUTTON_MINUS_Y, | ||
| 600 | R.integer.SWITCH_BUTTON_HOME_X, | ||
| 601 | R.integer.SWITCH_BUTTON_HOME_Y, | ||
| 602 | R.integer.SWITCH_BUTTON_CAPTURE_X, | ||
| 603 | R.integer.SWITCH_BUTTON_CAPTURE_Y, | ||
| 604 | R.integer.SWITCH_STICK_R_X, | ||
| 605 | R.integer.SWITCH_STICK_R_Y, | ||
| 606 | R.integer.SWITCH_STICK_L_X, | ||
| 607 | R.integer.SWITCH_STICK_L_Y | ||
| 608 | ) | ||
| 609 | |||
| 610 | private val portraitResources = arrayOf( | ||
| 611 | R.integer.SWITCH_BUTTON_A_X_PORTRAIT, | ||
| 612 | R.integer.SWITCH_BUTTON_A_Y_PORTRAIT, | ||
| 613 | R.integer.SWITCH_BUTTON_B_X_PORTRAIT, | ||
| 614 | R.integer.SWITCH_BUTTON_B_Y_PORTRAIT, | ||
| 615 | R.integer.SWITCH_BUTTON_X_X_PORTRAIT, | ||
| 616 | R.integer.SWITCH_BUTTON_X_Y_PORTRAIT, | ||
| 617 | R.integer.SWITCH_BUTTON_Y_X_PORTRAIT, | ||
| 618 | R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT, | ||
| 619 | R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT, | ||
| 620 | R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT, | ||
| 621 | R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT, | ||
| 622 | R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT, | ||
| 623 | R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT, | ||
| 624 | R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT, | ||
| 625 | R.integer.SWITCH_TRIGGER_L_X_PORTRAIT, | ||
| 626 | R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT, | ||
| 627 | R.integer.SWITCH_TRIGGER_R_X_PORTRAIT, | ||
| 628 | R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT, | ||
| 629 | R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT, | ||
| 630 | R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT, | ||
| 631 | R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT, | ||
| 632 | R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT, | ||
| 633 | R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT, | ||
| 634 | R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT, | ||
| 635 | R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT, | ||
| 636 | R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT, | ||
| 637 | R.integer.SWITCH_STICK_R_X_PORTRAIT, | ||
| 638 | R.integer.SWITCH_STICK_R_Y_PORTRAIT, | ||
| 639 | R.integer.SWITCH_STICK_L_X_PORTRAIT, | ||
| 640 | R.integer.SWITCH_STICK_L_Y_PORTRAIT | ||
| 641 | ) | ||
| 642 | |||
| 643 | private val foldableResources = arrayOf( | ||
| 644 | R.integer.SWITCH_BUTTON_A_X_FOLDABLE, | ||
| 645 | R.integer.SWITCH_BUTTON_A_Y_FOLDABLE, | ||
| 646 | R.integer.SWITCH_BUTTON_B_X_FOLDABLE, | ||
| 647 | R.integer.SWITCH_BUTTON_B_Y_FOLDABLE, | ||
| 648 | R.integer.SWITCH_BUTTON_X_X_FOLDABLE, | ||
| 649 | R.integer.SWITCH_BUTTON_X_Y_FOLDABLE, | ||
| 650 | R.integer.SWITCH_BUTTON_Y_X_FOLDABLE, | ||
| 651 | R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE, | ||
| 652 | R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE, | ||
| 653 | R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE, | ||
| 654 | R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE, | ||
| 655 | R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE, | ||
| 656 | R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE, | ||
| 657 | R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE, | ||
| 658 | R.integer.SWITCH_TRIGGER_L_X_FOLDABLE, | ||
| 659 | R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE, | ||
| 660 | R.integer.SWITCH_TRIGGER_R_X_FOLDABLE, | ||
| 661 | R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE, | ||
| 662 | R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE, | ||
| 663 | R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE, | ||
| 664 | R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE, | ||
| 665 | R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE, | ||
| 666 | R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE, | ||
| 667 | R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE, | ||
| 668 | R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE, | ||
| 669 | R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE, | ||
| 670 | R.integer.SWITCH_STICK_R_X_FOLDABLE, | ||
| 671 | R.integer.SWITCH_STICK_R_Y_FOLDABLE, | ||
| 672 | R.integer.SWITCH_STICK_L_X_FOLDABLE, | ||
| 673 | R.integer.SWITCH_STICK_L_Y_FOLDABLE | ||
| 674 | ) | ||
| 675 | |||
| 676 | private fun getResourceValue(orientation: String, position: Int): Float { | ||
| 677 | return when (orientation) { | ||
| 678 | PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 | ||
| 679 | FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 | ||
| 680 | else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 | ||
| 681 | } | ||
| 682 | } | ||
| 683 | |||
| 684 | private fun defaultOverlayByLayout(orientation: String) { | ||
| 577 | // Each value represents the position of the button in relation to the screen size without insets. | 685 | // Each value represents the position of the button in relation to the screen size without insets. |
| 578 | preferences.edit() | 686 | preferences.edit() |
| 579 | .putFloat( | 687 | .putFloat( |
| 580 | ButtonType.BUTTON_A.toString() + "-X", | 688 | ButtonType.BUTTON_A.toString() + "-X$orientation", |
| 581 | resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 | 689 | getResourceValue(orientation, 0) |
| 582 | ) | 690 | ) |
| 583 | .putFloat( | 691 | .putFloat( |
| 584 | ButtonType.BUTTON_A.toString() + "-Y", | 692 | ButtonType.BUTTON_A.toString() + "-Y$orientation", |
| 585 | resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 | 693 | getResourceValue(orientation, 1) |
| 586 | ) | 694 | ) |
| 587 | .putFloat( | 695 | .putFloat( |
| 588 | ButtonType.BUTTON_B.toString() + "-X", | 696 | ButtonType.BUTTON_B.toString() + "-X$orientation", |
| 589 | resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 | 697 | getResourceValue(orientation, 2) |
| 590 | ) | 698 | ) |
| 591 | .putFloat( | 699 | .putFloat( |
| 592 | ButtonType.BUTTON_B.toString() + "-Y", | 700 | ButtonType.BUTTON_B.toString() + "-Y$orientation", |
| 593 | resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 | 701 | getResourceValue(orientation, 3) |
| 594 | ) | 702 | ) |
| 595 | .putFloat( | 703 | .putFloat( |
| 596 | ButtonType.BUTTON_X.toString() + "-X", | 704 | ButtonType.BUTTON_X.toString() + "-X$orientation", |
| 597 | resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 | 705 | getResourceValue(orientation, 4) |
| 598 | ) | 706 | ) |
| 599 | .putFloat( | 707 | .putFloat( |
| 600 | ButtonType.BUTTON_X.toString() + "-Y", | 708 | ButtonType.BUTTON_X.toString() + "-Y$orientation", |
| 601 | resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 | 709 | getResourceValue(orientation, 5) |
| 602 | ) | 710 | ) |
| 603 | .putFloat( | 711 | .putFloat( |
| 604 | ButtonType.BUTTON_Y.toString() + "-X", | 712 | ButtonType.BUTTON_Y.toString() + "-X$orientation", |
| 605 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 | 713 | getResourceValue(orientation, 6) |
| 606 | ) | 714 | ) |
| 607 | .putFloat( | 715 | .putFloat( |
| 608 | ButtonType.BUTTON_Y.toString() + "-Y", | 716 | ButtonType.BUTTON_Y.toString() + "-Y$orientation", |
| 609 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 | 717 | getResourceValue(orientation, 7) |
| 610 | ) | 718 | ) |
| 611 | .putFloat( | 719 | .putFloat( |
| 612 | ButtonType.TRIGGER_ZL.toString() + "-X", | 720 | ButtonType.TRIGGER_ZL.toString() + "-X$orientation", |
| 613 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 | 721 | getResourceValue(orientation, 8) |
| 614 | ) | 722 | ) |
| 615 | .putFloat( | 723 | .putFloat( |
| 616 | ButtonType.TRIGGER_ZL.toString() + "-Y", | 724 | ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", |
| 617 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 | 725 | getResourceValue(orientation, 9) |
| 618 | ) | 726 | ) |
| 619 | .putFloat( | 727 | .putFloat( |
| 620 | ButtonType.TRIGGER_ZR.toString() + "-X", | 728 | ButtonType.TRIGGER_ZR.toString() + "-X$orientation", |
| 621 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 | 729 | getResourceValue(orientation, 10) |
| 622 | ) | 730 | ) |
| 623 | .putFloat( | 731 | .putFloat( |
| 624 | ButtonType.TRIGGER_ZR.toString() + "-Y", | 732 | ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", |
| 625 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 | 733 | getResourceValue(orientation, 11) |
| 626 | ) | 734 | ) |
| 627 | .putFloat( | 735 | .putFloat( |
| 628 | ButtonType.DPAD_UP.toString() + "-X", | 736 | ButtonType.DPAD_UP.toString() + "-X$orientation", |
| 629 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 | 737 | getResourceValue(orientation, 12) |
| 630 | ) | 738 | ) |
| 631 | .putFloat( | 739 | .putFloat( |
| 632 | ButtonType.DPAD_UP.toString() + "-Y", | 740 | ButtonType.DPAD_UP.toString() + "-Y$orientation", |
| 633 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 | 741 | getResourceValue(orientation, 13) |
| 634 | ) | 742 | ) |
| 635 | .putFloat( | 743 | .putFloat( |
| 636 | ButtonType.TRIGGER_L.toString() + "-X", | 744 | ButtonType.TRIGGER_L.toString() + "-X$orientation", |
| 637 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 | 745 | getResourceValue(orientation, 14) |
| 638 | ) | 746 | ) |
| 639 | .putFloat( | 747 | .putFloat( |
| 640 | ButtonType.TRIGGER_L.toString() + "-Y", | 748 | ButtonType.TRIGGER_L.toString() + "-Y$orientation", |
| 641 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 | 749 | getResourceValue(orientation, 15) |
| 642 | ) | 750 | ) |
| 643 | .putFloat( | 751 | .putFloat( |
| 644 | ButtonType.TRIGGER_R.toString() + "-X", | 752 | ButtonType.TRIGGER_R.toString() + "-X$orientation", |
| 645 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 | 753 | getResourceValue(orientation, 16) |
| 646 | ) | 754 | ) |
| 647 | .putFloat( | 755 | .putFloat( |
| 648 | ButtonType.TRIGGER_R.toString() + "-Y", | 756 | ButtonType.TRIGGER_R.toString() + "-Y$orientation", |
| 649 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 | 757 | getResourceValue(orientation, 17) |
| 650 | ) | 758 | ) |
| 651 | .putFloat( | 759 | .putFloat( |
| 652 | ButtonType.BUTTON_PLUS.toString() + "-X", | 760 | ButtonType.BUTTON_PLUS.toString() + "-X$orientation", |
| 653 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 | 761 | getResourceValue(orientation, 18) |
| 654 | ) | 762 | ) |
| 655 | .putFloat( | 763 | .putFloat( |
| 656 | ButtonType.BUTTON_PLUS.toString() + "-Y", | 764 | ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", |
| 657 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 | 765 | getResourceValue(orientation, 19) |
| 658 | ) | 766 | ) |
| 659 | .putFloat( | 767 | .putFloat( |
| 660 | ButtonType.BUTTON_MINUS.toString() + "-X", | 768 | ButtonType.BUTTON_MINUS.toString() + "-X$orientation", |
| 661 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 | 769 | getResourceValue(orientation, 20) |
| 662 | ) | 770 | ) |
| 663 | .putFloat( | 771 | .putFloat( |
| 664 | ButtonType.BUTTON_MINUS.toString() + "-Y", | 772 | ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", |
| 665 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 | 773 | getResourceValue(orientation, 21) |
| 666 | ) | 774 | ) |
| 667 | .putFloat( | 775 | .putFloat( |
| 668 | ButtonType.BUTTON_HOME.toString() + "-X", | 776 | ButtonType.BUTTON_HOME.toString() + "-X$orientation", |
| 669 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 | 777 | getResourceValue(orientation, 22) |
| 670 | ) | 778 | ) |
| 671 | .putFloat( | 779 | .putFloat( |
| 672 | ButtonType.BUTTON_HOME.toString() + "-Y", | 780 | ButtonType.BUTTON_HOME.toString() + "-Y$orientation", |
| 673 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 | 781 | getResourceValue(orientation, 23) |
| 674 | ) | 782 | ) |
| 675 | .putFloat( | 783 | .putFloat( |
| 676 | ButtonType.BUTTON_CAPTURE.toString() + "-X", | 784 | ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", |
| 677 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) | 785 | getResourceValue(orientation, 24) |
| 678 | .toFloat() / 1000 | ||
| 679 | ) | 786 | ) |
| 680 | .putFloat( | 787 | .putFloat( |
| 681 | ButtonType.BUTTON_CAPTURE.toString() + "-Y", | 788 | ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", |
| 682 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) | 789 | getResourceValue(orientation, 25) |
| 683 | .toFloat() / 1000 | ||
| 684 | ) | 790 | ) |
| 685 | .putFloat( | 791 | .putFloat( |
| 686 | ButtonType.STICK_R.toString() + "-X", | 792 | ButtonType.STICK_R.toString() + "-X$orientation", |
| 687 | resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 | 793 | getResourceValue(orientation, 26) |
| 688 | ) | 794 | ) |
| 689 | .putFloat( | 795 | .putFloat( |
| 690 | ButtonType.STICK_R.toString() + "-Y", | 796 | ButtonType.STICK_R.toString() + "-Y$orientation", |
| 691 | resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 | 797 | getResourceValue(orientation, 27) |
| 692 | ) | 798 | ) |
| 693 | .putFloat( | 799 | .putFloat( |
| 694 | ButtonType.STICK_L.toString() + "-X", | 800 | ButtonType.STICK_L.toString() + "-X$orientation", |
| 695 | resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 | 801 | getResourceValue(orientation, 28) |
| 696 | ) | 802 | ) |
| 697 | .putFloat( | 803 | .putFloat( |
| 698 | ButtonType.STICK_L.toString() + "-Y", | 804 | ButtonType.STICK_L.toString() + "-Y$orientation", |
| 699 | resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 | 805 | getResourceValue(orientation, 29) |
| 700 | ) | 806 | ) |
| 701 | .apply() | 807 | .apply() |
| 702 | } | 808 | } |
| @@ -709,13 +815,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 709 | private val preferences: SharedPreferences = | 815 | private val preferences: SharedPreferences = |
| 710 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 816 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 711 | 817 | ||
| 818 | const val LANDSCAPE = "" | ||
| 819 | const val PORTRAIT = "_Portrait" | ||
| 820 | const val FOLDABLE = "_Foldable" | ||
| 821 | |||
| 712 | /** | 822 | /** |
| 713 | * Resizes a [Bitmap] by a given scale factor | 823 | * Resizes a [Bitmap] by a given scale factor |
| 714 | * | 824 | * |
| 715 | * @param context Context for getting the vector drawable | 825 | * @param context Context for getting the vector drawable |
| 716 | * @param drawableId The ID of the drawable to scale. | 826 | * @param drawableId The ID of the drawable to scale. |
| 717 | * @param scale The scale factor for the bitmap. | 827 | * @param scale The scale factor for the bitmap. |
| 718 | * @return The scaled [Bitmap] | 828 | * @return The scaled [Bitmap] |
| 719 | */ | 829 | */ |
| 720 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { | 830 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { |
| 721 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable | 831 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable |
| @@ -749,14 +859,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 749 | * Gets the safe screen size for drawing the overlay | 859 | * Gets the safe screen size for drawing the overlay |
| 750 | * | 860 | * |
| 751 | * @param context Context for getting the window metrics | 861 | * @param context Context for getting the window metrics |
| 752 | * @return A pair of points, the first being the top left corner of the safe area, | 862 | * @return A pair of points, the first being the top left corner of the safe area, |
| 753 | * the second being the bottom right corner of the safe area | 863 | * the second being the bottom right corner of the safe area |
| 754 | */ | 864 | */ |
| 755 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { | 865 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { |
| 756 | // Get screen size | 866 | // Get screen size |
| 757 | val windowMetrics = | 867 | val windowMetrics = WindowMetricsCalculator.getOrCreate() |
| 758 | WindowMetricsCalculator.getOrCreate() | 868 | .computeCurrentWindowMetrics(context as Activity) |
| 759 | .computeCurrentWindowMetrics(context as Activity) | ||
| 760 | var maxY = windowMetrics.bounds.height().toFloat() | 869 | var maxY = windowMetrics.bounds.height().toFloat() |
| 761 | var maxX = windowMetrics.bounds.width().toFloat() | 870 | var maxX = windowMetrics.bounds.width().toFloat() |
| 762 | var minY = 0 | 871 | var minY = 0 |
| @@ -768,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 768 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | 877 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
| 769 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout | 878 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout |
| 770 | if (insets != null) { | 879 | if (insets != null) { |
| 771 | if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) | 880 | if (insets.boundingRectTop.bottom != 0 && |
| 772 | insets.boundingRectTop.bottom.toFloat() else maxY | 881 | insets.boundingRectTop.bottom > maxY / 2 |
| 773 | if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) | 882 | ) { |
| 774 | insets.boundingRectRight.left.toFloat() else maxX | 883 | maxY = insets.boundingRectTop.bottom.toFloat() |
| 884 | } | ||
| 885 | if (insets.boundingRectRight.left != 0 && | ||
| 886 | insets.boundingRectRight.left > maxX / 2 | ||
| 887 | ) { | ||
| 888 | maxX = insets.boundingRectRight.left.toFloat() | ||
| 889 | } | ||
| 775 | 890 | ||
| 776 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left | 891 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left |
| 777 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom | 892 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom |
| @@ -878,8 +993,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 878 | 993 | ||
| 879 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 994 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 880 | // These were set in the input overlay configuration menu. | 995 | // These were set in the input overlay configuration menu. |
| 881 | val xKey = "$buttonId$orientation-X" | 996 | val xKey = "$buttonId-X$orientation" |
| 882 | val yKey = "$buttonId$orientation-Y" | 997 | val yKey = "$buttonId-Y$orientation" |
| 883 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) | 998 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) |
| 884 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) | 999 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) |
| 885 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1000 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| @@ -959,8 +1074,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 959 | 1074 | ||
| 960 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. | 1075 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. |
| 961 | // These were set in the input overlay configuration menu. | 1076 | // These were set in the input overlay configuration menu. |
| 962 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f) | 1077 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) |
| 963 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f) | 1078 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) |
| 964 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1079 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 965 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1080 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 966 | val width = overlayDrawable.width | 1081 | val width = overlayDrawable.width |
| @@ -1026,8 +1141,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 1026 | 1141 | ||
| 1027 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 1142 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 1028 | // These were set in the input overlay configuration menu. | 1143 | // These were set in the input overlay configuration menu. |
| 1029 | val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f) | 1144 | val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) |
| 1030 | val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f) | 1145 | val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) |
| 1031 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1146 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 1032 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1147 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 1033 | val outerScale = 1.66f | 1148 | val outerScale = 1.66f |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 43d664d21..8aef6f5a5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt | |||
| @@ -133,7 +133,10 @@ class InputOverlayDrawableDpad( | |||
| 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE | 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE |
| 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE | 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE |
| 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE | 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE |
| 136 | return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState | 136 | return oldUpState != upButtonState || |
| 137 | oldDownState != downButtonState || | ||
| 138 | oldLeftState != leftButtonState || | ||
| 139 | oldRightState != rightButtonState | ||
| 137 | } | 140 | } |
| 138 | return false | 141 | return false |
| 139 | } | 142 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index f1d32192a..fb48f584d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt | |||
| @@ -9,12 +9,12 @@ import android.graphics.Canvas | |||
| 9 | import android.graphics.Rect | 9 | import android.graphics.Rect |
| 10 | import android.graphics.drawable.BitmapDrawable | 10 | import android.graphics.drawable.BitmapDrawable |
| 11 | import android.view.MotionEvent | 11 | import android.view.MotionEvent |
| 12 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 13 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 14 | import kotlin.math.atan2 | 12 | import kotlin.math.atan2 |
| 15 | import kotlin.math.cos | 13 | import kotlin.math.cos |
| 16 | import kotlin.math.sin | 14 | import kotlin.math.sin |
| 17 | import kotlin.math.sqrt | 15 | import kotlin.math.sqrt |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Custom [BitmapDrawable] that is capable | 20 | * Custom [BitmapDrawable] that is capable |
| @@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick( | |||
| 241 | private fun setInnerBounds() { | 241 | private fun setInnerBounds() { |
| 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() | 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() |
| 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() | 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() |
| 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) x = | 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) { |
| 245 | virtBounds.centerX() + virtBounds.width() / 2 | 245 | x = |
| 246 | if (x < virtBounds.centerX() - virtBounds.width() / 2) x = | 246 | virtBounds.centerX() + virtBounds.width() / 2 |
| 247 | virtBounds.centerX() - virtBounds.width() / 2 | 247 | } |
| 248 | if (y > virtBounds.centerY() + virtBounds.height() / 2) y = | 248 | if (x < virtBounds.centerX() - virtBounds.width() / 2) { |
| 249 | virtBounds.centerY() + virtBounds.height() / 2 | 249 | x = |
| 250 | if (y < virtBounds.centerY() - virtBounds.height() / 2) y = | 250 | virtBounds.centerX() - virtBounds.width() / 2 |
| 251 | virtBounds.centerY() - virtBounds.height() / 2 | 251 | } |
| 252 | if (y > virtBounds.centerY() + virtBounds.height() / 2) { | ||
| 253 | y = | ||
| 254 | virtBounds.centerY() + virtBounds.height() / 2 | ||
| 255 | } | ||
| 256 | if (y < virtBounds.centerY() - virtBounds.height() / 2) { | ||
| 257 | y = | ||
| 258 | virtBounds.centerY() - virtBounds.height() / 2 | ||
| 259 | } | ||
| 252 | val width = pressedStateInnerBitmap.bounds.width() / 2 | 260 | val width = pressedStateInnerBitmap.bounds.width() / 2 |
| 253 | val height = pressedStateInnerBitmap.bounds.height() / 2 | 261 | val height = pressedStateInnerBitmap.bounds.height() / 2 |
| 254 | defaultStateInnerBitmap.setBounds( | 262 | defaultStateInnerBitmap.setBounds( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 97eef40d2..b0156dca5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt | |||
| @@ -99,7 +99,9 @@ class GamesFragment : Fragment() { | |||
| 99 | } | 99 | } |
| 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> | 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> |
| 101 | if (shouldSwapData) { | 101 | if (shouldSwapData) { |
| 102 | (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) | 102 | (binding.gridGames.adapter as GameAdapter).submitList( |
| 103 | gamesViewModel.games.value!! | ||
| 104 | ) | ||
| 103 | gamesViewModel.setShouldSwapData(false) | 105 | gamesViewModel.setShouldSwapData(false) |
| 104 | } | 106 | } |
| 105 | } | 107 | } |
| @@ -128,7 +130,9 @@ class GamesFragment : Fragment() { | |||
| 128 | } | 130 | } |
| 129 | 131 | ||
| 130 | private fun setInsets() = | 132 | private fun setInsets() = |
| 131 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 133 | ViewCompat.setOnApplyWindowInsetsListener( |
| 134 | binding.root | ||
| 135 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 132 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 136 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 133 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 137 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 134 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) | 138 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 041d16f3a..cc1d87f1b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager | |||
| 26 | import com.google.android.material.color.MaterialColors | 26 | import com.google.android.material.color.MaterialColors |
| 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 28 | import com.google.android.material.navigation.NavigationBarView | 28 | import com.google.android.material.navigation.NavigationBarView |
| 29 | import java.io.File | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.io.IOException | ||
| 29 | import kotlinx.coroutines.Dispatchers | 32 | import kotlinx.coroutines.Dispatchers |
| 30 | import kotlinx.coroutines.launch | 33 | import kotlinx.coroutines.launch |
| 31 | import kotlinx.coroutines.withContext | 34 | import kotlinx.coroutines.withContext |
| @@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | |||
| 43 | import org.yuzu.yuzu_emu.model.GamesViewModel | 46 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 44 | import org.yuzu.yuzu_emu.model.HomeViewModel | 47 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 45 | import org.yuzu.yuzu_emu.utils.* | 48 | import org.yuzu.yuzu_emu.utils.* |
| 46 | import java.io.File | ||
| 47 | import java.io.FilenameFilter | ||
| 48 | import java.io.IOException | ||
| 49 | 49 | ||
| 50 | class MainActivity : AppCompatActivity(), ThemeProvider { | 50 | class MainActivity : AppCompatActivity(), ThemeProvider { |
| 51 | private lateinit var binding: ActivityMainBinding | 51 | private lateinit var binding: ActivityMainBinding |
| @@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 86 | ThemeHelper.SYSTEM_BAR_ALPHA | 86 | ThemeHelper.SYSTEM_BAR_ALPHA |
| 87 | ) | 87 | ) |
| 88 | ) | 88 | ) |
| 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 90 | InsetsHelper.GESTURE_NAVIGATION | ||
| 91 | ) { | ||
| 90 | binding.navigationBarShade.setBackgroundColor( | 92 | binding.navigationBarShade.setBackgroundColor( |
| 91 | ThemeHelper.getColorWithOpacity( | 93 | ThemeHelper.getColorWithOpacity( |
| 92 | MaterialColors.getColor( | 94 | MaterialColors.getColor( |
| @@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 172 | binding.navigationView.height.toFloat() * 2 | 174 | binding.navigationView.height.toFloat() * 2 |
| 173 | translationY(0f) | 175 | translationY(0f) |
| 174 | } else { | 176 | } else { |
| 175 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 177 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 178 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 179 | ) { | ||
| 176 | binding.navigationView.translationX = | 180 | binding.navigationView.translationX = |
| 177 | binding.navigationView.width.toFloat() * -2 | 181 | binding.navigationView.width.toFloat() * -2 |
| 178 | translationX(0f) | 182 | translationX(0f) |
| @@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 189 | if (smallLayout) { | 193 | if (smallLayout) { |
| 190 | translationY(binding.navigationView.height.toFloat() * 2) | 194 | translationY(binding.navigationView.height.toFloat() * 2) |
| 191 | } else { | 195 | } else { |
| 192 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 196 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 197 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 198 | ) { | ||
| 193 | translationX(binding.navigationView.width.toFloat() * -2) | 199 | translationX(binding.navigationView.width.toFloat() * -2) |
| 194 | } else { | 200 | } else { |
| 195 | translationX(binding.navigationView.width.toFloat() * 2) | 201 | translationX(binding.navigationView.width.toFloat() * 2) |
| @@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 234 | } | 240 | } |
| 235 | 241 | ||
| 236 | private fun setInsets() = | 242 | private fun setInsets() = |
| 237 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 243 | ViewCompat.setOnApplyWindowInsetsListener( |
| 244 | binding.root | ||
| 245 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 238 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 246 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 239 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams | 247 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams |
| 240 | mlpStatusShade.height = insets.top | 248 | mlpStatusShade.height = insets.top |
| @@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 256 | 264 | ||
| 257 | val getGamesDirectory = | 265 | val getGamesDirectory = |
| 258 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | 266 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> |
| 259 | if (result == null) | 267 | if (result == null) { |
| 260 | return@registerForActivityResult | 268 | return@registerForActivityResult |
| 269 | } | ||
| 261 | 270 | ||
| 262 | contentResolver.takePersistableUriPermission( | 271 | contentResolver.takePersistableUriPermission( |
| 263 | result, | 272 | result, |
| @@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 281 | 290 | ||
| 282 | val getProdKey = | 291 | val getProdKey = |
| 283 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 292 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 284 | if (result == null) | 293 | if (result == null) { |
| 285 | return@registerForActivityResult | 294 | return@registerForActivityResult |
| 295 | } | ||
| 286 | 296 | ||
| 287 | if (!FileUtil.hasExtension(result, "keys")) { | 297 | if (!FileUtil.hasExtension(result, "keys")) { |
| 288 | MessageDialogFragment.newInstance( | 298 | MessageDialogFragment.newInstance( |
| @@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 324 | 334 | ||
| 325 | val getFirmware = | 335 | val getFirmware = |
| 326 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 336 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 327 | if (result == null) | 337 | if (result == null) { |
| 328 | return@registerForActivityResult | 338 | return@registerForActivityResult |
| 339 | } | ||
| 329 | 340 | ||
| 330 | val inputZip = contentResolver.openInputStream(result) | 341 | val inputZip = contentResolver.openInputStream(result) |
| 331 | if (inputZip == null) { | 342 | if (inputZip == null) { |
| @@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 376 | 387 | ||
| 377 | val getAmiiboKey = | 388 | val getAmiiboKey = |
| 378 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 389 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 379 | if (result == null) | 390 | if (result == null) { |
| 380 | return@registerForActivityResult | 391 | return@registerForActivityResult |
| 392 | } | ||
| 381 | 393 | ||
| 382 | if (!FileUtil.hasExtension(result, "bin")) { | 394 | if (!FileUtil.hasExtension(result, "bin")) { |
| 383 | MessageDialogFragment.newInstance( | 395 | MessageDialogFragment.newInstance( |
| @@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 418 | 430 | ||
| 419 | val getDriver = | 431 | val getDriver = |
| 420 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 432 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 421 | if (result == null) | 433 | if (result == null) { |
| 422 | return@registerForActivityResult | 434 | return@registerForActivityResult |
| 435 | } | ||
| 423 | 436 | ||
| 424 | val takeFlags = | 437 | val takeFlags = |
| 425 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION | 438 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION |
| @@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 470 | 483 | ||
| 471 | val installGameUpdate = | 484 | val installGameUpdate = |
| 472 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { | 485 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { |
| 473 | if (it == null) | 486 | if (it == null) { |
| 474 | return@registerForActivityResult | 487 | return@registerForActivityResult |
| 488 | } | ||
| 475 | 489 | ||
| 476 | IndeterminateProgressDialogFragment.newInstance( | 490 | IndeterminateProgressDialogFragment.newInstance( |
| 477 | this@MainActivity, | 491 | this@MainActivity, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt index 791cea904..eeefcdf20 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt | |||
| @@ -19,7 +19,9 @@ class ControllerMappingHelper { | |||
| 19 | // The two analog triggers generate analog motion events as well as a keycode. | 19 | // The two analog triggers generate analog motion events as well as a keycode. |
| 20 | // We always prefer to use the analog values, so throw away the button press | 20 | // We always prefer to use the analog values, so throw away the button press |
| 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 | 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 |
| 22 | } else false | 22 | } else { |
| 23 | false | ||
| 24 | } | ||
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | /** | 27 | /** |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 36c479e6c..2ee63697e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt | |||
| @@ -4,8 +4,8 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 8 | import java.io.IOException | 7 | import java.io.IOException |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | 9 | ||
| 10 | object DirectoryInitialization { | 10 | object DirectoryInitialization { |
| 11 | private var userPath: String? = null | 11 | private var userPath: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index cc8ea6b9d..cf226ad94 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt | |||
| @@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.net.Uri | 6 | import android.net.Uri |
| 7 | import androidx.documentfile.provider.DocumentFile | 7 | import androidx.documentfile.provider.DocumentFile |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 9 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 10 | import java.io.File | 8 | import java.io.File |
| 11 | import java.util.* | 9 | import java.util.* |
| 10 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 11 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 12 | 12 | ||
| 13 | class DocumentsTree { | 13 | class DocumentsTree { |
| 14 | private var root: DocumentsNode? = null | 14 | private var root: DocumentsNode? = null |
| @@ -29,13 +29,20 @@ class DocumentsTree { | |||
| 29 | val node = resolvePath(filepath) | 29 | val node = resolvePath(filepath) |
| 30 | return if (node == null || node.isDirectory) { | 30 | return if (node == null || node.isDirectory) { |
| 31 | 0 | 31 | 0 |
| 32 | } else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | 32 | } else { |
| 33 | FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | ||
| 34 | } | ||
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | fun exists(filepath: String): Boolean { | 37 | fun exists(filepath: String): Boolean { |
| 36 | return resolvePath(filepath) != null | 38 | return resolvePath(filepath) != null |
| 37 | } | 39 | } |
| 38 | 40 | ||
| 41 | fun isDirectory(filepath: String): Boolean { | ||
| 42 | val node = resolvePath(filepath) | ||
| 43 | return node != null && node.isDirectory | ||
| 44 | } | ||
| 45 | |||
| 39 | private fun resolvePath(filepath: String): DocumentsNode? { | 46 | private fun resolvePath(filepath: String): DocumentsNode? { |
| 40 | val tokens = StringTokenizer(filepath, File.separator, false) | 47 | val tokens = StringTokenizer(filepath, File.separator, false) |
| 41 | var iterator = root | 48 | var iterator = root |
| @@ -106,7 +113,9 @@ class DocumentsTree { | |||
| 106 | fun isNativePath(path: String): Boolean { | 113 | fun isNativePath(path: String): Boolean { |
| 107 | return if (path.isNotEmpty()) { | 114 | return if (path.isNotEmpty()) { |
| 108 | path[0] == '/' | 115 | path[0] == '/' |
| 109 | } else false | 116 | } else { |
| 117 | false | ||
| 118 | } | ||
| 110 | } | 119 | } |
| 111 | } | 120 | } |
| 112 | } | 121 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt index e1e7a59d7..7e8f058c1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt | |||
| @@ -11,14 +11,6 @@ object EmulationMenuSettings { | |||
| 11 | private val preferences = | 11 | private val preferences = |
| 12 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 12 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 13 | 13 | ||
| 14 | // These must match what is defined in src/core/settings.h | ||
| 15 | const val LayoutOption_Default = 0 | ||
| 16 | const val LayoutOption_SingleScreen = 1 | ||
| 17 | const val LayoutOption_LargeScreen = 2 | ||
| 18 | const val LayoutOption_SideScreen = 3 | ||
| 19 | const val LayoutOption_MobilePortrait = 4 | ||
| 20 | const val LayoutOption_MobileLandscape = 5 | ||
| 21 | |||
| 22 | var joystickRelCenter: Boolean | 14 | var joystickRelCenter: Boolean |
| 23 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) | 15 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) |
| 24 | set(value) { | 16 | set(value) { |
| @@ -41,16 +33,6 @@ object EmulationMenuSettings { | |||
| 41 | .apply() | 33 | .apply() |
| 42 | } | 34 | } |
| 43 | 35 | ||
| 44 | var landscapeScreenLayout: Int | ||
| 45 | get() = preferences.getInt( | ||
| 46 | Settings.PREF_MENU_SETTINGS_LANDSCAPE, | ||
| 47 | LayoutOption_MobileLandscape | ||
| 48 | ) | ||
| 49 | set(value) { | ||
| 50 | preferences.edit() | ||
| 51 | .putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value) | ||
| 52 | .apply() | ||
| 53 | } | ||
| 54 | var showFps: Boolean | 36 | var showFps: Boolean |
| 55 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) | 37 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) |
| 56 | set(value) { | 38 | set(value) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 492b1ad91..9f3bbe56f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt | |||
| @@ -9,8 +9,6 @@ import android.net.Uri | |||
| 9 | import android.provider.DocumentsContract | 9 | import android.provider.DocumentsContract |
| 10 | import android.provider.OpenableColumns | 10 | import android.provider.OpenableColumns |
| 11 | import androidx.documentfile.provider.DocumentFile | 11 | import androidx.documentfile.provider.DocumentFile |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 13 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 14 | import java.io.BufferedInputStream | 12 | import java.io.BufferedInputStream |
| 15 | import java.io.File | 13 | import java.io.File |
| 16 | import java.io.FileOutputStream | 14 | import java.io.FileOutputStream |
| @@ -19,6 +17,8 @@ import java.io.InputStream | |||
| 19 | import java.net.URLDecoder | 17 | import java.net.URLDecoder |
| 20 | import java.util.zip.ZipEntry | 18 | import java.util.zip.ZipEntry |
| 21 | import java.util.zip.ZipInputStream | 19 | import java.util.zip.ZipInputStream |
| 20 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 21 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 22 | 22 | ||
| 23 | object FileUtil { | 23 | object FileUtil { |
| 24 | const val PATH_TREE = "tree" | 24 | const val PATH_TREE = "tree" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt index dc9b7c744..086d17606 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt | |||
| @@ -54,7 +54,7 @@ class ForegroundService : Service() { | |||
| 54 | 54 | ||
| 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { | 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
| 56 | if (intent == null) { | 56 | if (intent == null) { |
| 57 | return START_NOT_STICKY; | 57 | return START_NOT_STICKY |
| 58 | } | 58 | } |
| 59 | if (intent.action == ACTION_STOP) { | 59 | if (intent.action == ACTION_STOP) { |
| 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) | 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 42b207618..ee9f3e570 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils | |||
| 6 | import android.content.SharedPreferences | 6 | import android.content.SharedPreferences |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import androidx.preference.PreferenceManager | 8 | import androidx.preference.PreferenceManager |
| 9 | import java.util.* | ||
| 9 | import kotlinx.serialization.encodeToString | 10 | import kotlinx.serialization.encodeToString |
| 10 | import kotlinx.serialization.json.Json | 11 | import kotlinx.serialization.json.Json |
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 12 | import org.yuzu.yuzu_emu.NativeLibrary |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 13 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.model.Game | 14 | import org.yuzu.yuzu_emu.model.Game |
| 14 | import java.util.* | ||
| 15 | 15 | ||
| 16 | object GameHelper { | 16 | object GameHelper { |
| 17 | const val KEY_GAME_PATH = "game_path" | 17 | const val KEY_GAME_PATH = "game_path" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index 528011d7f..1d4695a2a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt | |||
| @@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 10 | import java.io.BufferedInputStream | 8 | import java.io.BufferedInputStream |
| 11 | import java.io.File | 9 | import java.io.File |
| 12 | import java.io.FileInputStream | 10 | import java.io.FileInputStream |
| 13 | import java.io.FileOutputStream | 11 | import java.io.FileOutputStream |
| 14 | import java.io.IOException | 12 | import java.io.IOException |
| 15 | import java.util.zip.ZipInputStream | 13 | import java.util.zip.ZipInputStream |
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 15 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 16 | 16 | ||
| 17 | object GpuDriverHelper { | 17 | object GpuDriverHelper { |
| 18 | private const val META_JSON_FILENAME = "meta.json" | 18 | private const val META_JSON_FILENAME = "meta.json" |
| @@ -113,6 +113,8 @@ object GpuDriverHelper { | |||
| 113 | initializeDriverParameters(context) | 113 | initializeDriverParameters(context) |
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | external fun supportsCustomDriverLoading(): Boolean | ||
| 117 | |||
| 116 | // Parse the custom driver metadata to retrieve the name. | 118 | // Parse the custom driver metadata to retrieve the name. |
| 117 | val customDriverName: String? | 119 | val customDriverName: String? |
| 118 | get() { | 120 | get() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt index 70bdb4097..a4e64070a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt | |||
| @@ -3,12 +3,12 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import org.json.JSONException | ||
| 7 | import org.json.JSONObject | ||
| 8 | import java.io.IOException | 6 | import java.io.IOException |
| 9 | import java.nio.charset.StandardCharsets | 7 | import java.nio.charset.StandardCharsets |
| 10 | import java.nio.file.Files | 8 | import java.nio.file.Files |
| 11 | import java.nio.file.Paths | 9 | import java.nio.file.Paths |
| 10 | import org.json.JSONException | ||
| 11 | import org.json.JSONObject | ||
| 12 | 12 | ||
| 13 | class GpuDriverMetadata(metadataFilePath: String) { | 13 | class GpuDriverMetadata(metadataFilePath: String) { |
| 14 | var name: String? = null | 14 | var name: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index 24e999b29..e963dfbc1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt | |||
| @@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.view.KeyEvent | 6 | import android.view.KeyEvent |
| 7 | import android.view.MotionEvent | 7 | import android.view.MotionEvent |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import kotlin.math.sqrt | 8 | import kotlin.math.sqrt |
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 10 | 10 | ||
| 11 | class InputHandler { | 11 | class InputHandler { |
| 12 | fun initialize() { | 12 | fun initialize() { |
| @@ -68,7 +68,11 @@ class InputHandler { | |||
| 68 | 6 -> NativeLibrary.Player6Device | 68 | 6 -> NativeLibrary.Player6Device |
| 69 | 7 -> NativeLibrary.Player7Device | 69 | 7 -> NativeLibrary.Player7Device |
| 70 | 8 -> NativeLibrary.Player8Device | 70 | 8 -> NativeLibrary.Player8Device |
| 71 | else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 71 | else -> if (NativeLibrary.isHandheldOnly()) { |
| 72 | NativeLibrary.ConsoleDevice | ||
| 73 | } else { | ||
| 74 | NativeLibrary.Player1Device | ||
| 75 | } | ||
| 72 | } | 76 | } |
| 73 | } | 77 | } |
| 74 | 78 | ||
| @@ -107,7 +111,11 @@ class InputHandler { | |||
| 107 | } | 111 | } |
| 108 | 112 | ||
| 109 | private fun getAxisToButton(axis: Float): Int { | 113 | private fun getAxisToButton(axis: Float): Int { |
| 110 | return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED | 114 | return if (axis > 0.5f) { |
| 115 | NativeLibrary.ButtonState.PRESSED | ||
| 116 | } else { | ||
| 117 | NativeLibrary.ButtonState.RELEASED | ||
| 118 | } | ||
| 111 | } | 119 | } |
| 112 | 120 | ||
| 113 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { | 121 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { |
| @@ -287,7 +295,6 @@ class InputHandler { | |||
| 287 | } | 295 | } |
| 288 | } | 296 | } |
| 289 | 297 | ||
| 290 | |||
| 291 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { | 298 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { |
| 292 | // Joycon support is half dead. Right joystick doesn't work | 299 | // Joycon support is half dead. Right joystick doesn't work |
| 293 | val playerNumber = getPlayerNumber(event.device.controllerNumber) | 300 | val playerNumber = getPlayerNumber(event.device.controllerNumber) |
| @@ -355,6 +362,4 @@ class InputHandler { | |||
| 355 | ) | 362 | ) |
| 356 | } | 363 | } |
| 357 | } | 364 | } |
| 358 | 365 | } | |
| 359 | |||
| 360 | } \ No newline at end of file | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt index 19c53c481..595f0d284 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt | |||
| @@ -4,9 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | 6 | import android.annotation.SuppressLint |
| 7 | import android.app.Activity | ||
| 8 | import android.content.Context | 7 | import android.content.Context |
| 9 | import android.graphics.Rect | ||
| 10 | 8 | ||
| 11 | object InsetsHelper { | 9 | object InsetsHelper { |
| 12 | const val THREE_BUTTON_NAVIGATION = 0 | 10 | const val THREE_BUTTON_NAVIGATION = 0 |
| @@ -20,12 +18,8 @@ object InsetsHelper { | |||
| 20 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") | 18 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") |
| 21 | return if (resourceId != 0) { | 19 | return if (resourceId != 0) { |
| 22 | resources.getInteger(resourceId) | 20 | resources.getInteger(resourceId) |
| 23 | } else 0 | 21 | } else { |
| 24 | } | 22 | 0 |
| 25 | 23 | } | |
| 26 | fun getBottomPaddingRequired(activity: Activity): Int { | ||
| 27 | val visibleFrame = Rect() | ||
| 28 | activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame) | ||
| 29 | return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels | ||
| 30 | } | 24 | } |
| 31 | } | 25 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt index 344dd8a0a..68ed66565 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt | |||
| @@ -13,8 +13,8 @@ import android.nfc.tech.NfcA | |||
| 13 | import android.os.Build | 13 | import android.os.Build |
| 14 | import android.os.Handler | 14 | import android.os.Handler |
| 15 | import android.os.Looper | 15 | import android.os.Looper |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import java.io.IOException | 16 | import java.io.IOException |
| 17 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 18 | 18 | ||
| 19 | class NfcReader(private val activity: Activity) { | 19 | class NfcReader(private val activity: Activity) { |
| 20 | private var nfcAdapter: NfcAdapter? = null | 20 | private var nfcAdapter: NfcAdapter? = null |
| @@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) { | |||
| 25 | 25 | ||
| 26 | pendingIntent = PendingIntent.getActivity( | 26 | pendingIntent = PendingIntent.getActivity( |
| 27 | activity, | 27 | activity, |
| 28 | 0, Intent(activity, activity.javaClass), | 28 | 0, |
| 29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) | 29 | Intent(activity, activity.javaClass), |
| 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 30 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE | 31 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE |
| 31 | else PendingIntent.FLAG_UPDATE_CURRENT | 32 | } else { |
| 33 | PendingIntent.FLAG_UPDATE_CURRENT | ||
| 34 | } | ||
| 32 | ) | 35 | ) |
| 33 | 36 | ||
| 34 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) | 37 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) |
| @@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) { | |||
| 45 | 48 | ||
| 46 | fun onNewIntent(intent: Intent) { | 49 | fun onNewIntent(intent: Intent) { |
| 47 | val action = intent.action | 50 | val action = intent.action |
| 48 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action | 51 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action && |
| 49 | && NfcAdapter.ACTION_TECH_DISCOVERED != action | 52 | NfcAdapter.ACTION_TECH_DISCOVERED != action && |
| 50 | && NfcAdapter.ACTION_NDEF_DISCOVERED != action | 53 | NfcAdapter.ACTION_NDEF_DISCOVERED != action |
| 51 | ) { | 54 | ) { |
| 52 | return | 55 | return |
| 53 | } | 56 | } |
| @@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) { | |||
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { | 89 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { |
| 87 | val bufferSize = amiibo.maxTransceiveLength; | 90 | val bufferSize = amiibo.maxTransceiveLength |
| 88 | val tagSize = 0x21C | 91 | val tagSize = 0x21C |
| 89 | val pageSize = 4 | 92 | val pageSize = 4 |
| 90 | val lastPage = tagSize / pageSize - 1 | 93 | val lastPage = tagSize / pageSize - 1 |
| @@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) { | |||
| 103 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) | 106 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) |
| 104 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) | 107 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) |
| 105 | } catch (e: IOException) { | 108 | } catch (e: IOException) { |
| 106 | return null; | 109 | return null |
| 107 | } | 110 | } |
| 108 | } | 111 | } |
| 109 | return tagData | 112 | return tagData |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt index 87ee7f2e6..00e58faec 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt | |||
| @@ -11,30 +11,34 @@ import java.io.Serializable | |||
| 11 | 11 | ||
| 12 | object SerializableHelper { | 12 | object SerializableHelper { |
| 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { | 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { |
| 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 15 | getSerializable(key, T::class.java) | 15 | getSerializable(key, T::class.java) |
| 16 | else | 16 | } else { |
| 17 | getSerializable(key) as? T | 17 | getSerializable(key) as? T |
| 18 | } | ||
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { | 21 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { |
| 21 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 22 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 22 | getSerializableExtra(key, T::class.java) | 23 | getSerializableExtra(key, T::class.java) |
| 23 | else | 24 | } else { |
| 24 | getSerializableExtra(key) as? T | 25 | getSerializableExtra(key) as? T |
| 26 | } | ||
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { | 29 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { |
| 28 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 30 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 29 | getParcelable(key, T::class.java) | 31 | getParcelable(key, T::class.java) |
| 30 | else | 32 | } else { |
| 31 | getParcelable(key) as? T | 33 | getParcelable(key) as? T |
| 34 | } | ||
| 32 | } | 35 | } |
| 33 | 36 | ||
| 34 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { | 37 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { |
| 35 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 38 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 36 | getParcelableExtra(key, T::class.java) | 39 | getParcelableExtra(key, T::class.java) |
| 37 | else | 40 | } else { |
| 38 | getParcelableExtra(key) as? T | 41 | getParcelableExtra(key) as? T |
| 42 | } | ||
| 39 | } | 43 | } |
| 40 | } | 44 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt index e55767c0f..f312e24cf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt | |||
| @@ -3,21 +3,19 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.app.Activity | ||
| 7 | import android.content.res.Configuration | 6 | import android.content.res.Configuration |
| 8 | import android.graphics.Color | 7 | import android.graphics.Color |
| 9 | import androidx.annotation.ColorInt | 8 | import androidx.annotation.ColorInt |
| 10 | import androidx.appcompat.app.AppCompatActivity | 9 | import androidx.appcompat.app.AppCompatActivity |
| 11 | import androidx.appcompat.app.AppCompatDelegate | 10 | import androidx.appcompat.app.AppCompatDelegate |
| 12 | import androidx.core.content.ContextCompat | ||
| 13 | import androidx.core.view.WindowCompat | 11 | import androidx.core.view.WindowCompat |
| 14 | import androidx.core.view.WindowInsetsControllerCompat | 12 | import androidx.core.view.WindowInsetsControllerCompat |
| 15 | import androidx.preference.PreferenceManager | 13 | import androidx.preference.PreferenceManager |
| 14 | import kotlin.math.roundToInt | ||
| 16 | import org.yuzu.yuzu_emu.R | 15 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.YuzuApplication | 16 | import org.yuzu.yuzu_emu.YuzuApplication |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 17 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 19 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider | 18 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider |
| 20 | import kotlin.math.roundToInt | ||
| 21 | 19 | ||
| 22 | object ThemeHelper { | 20 | object ThemeHelper { |
| 23 | const val SYSTEM_BAR_ALPHA = 0.9f | 21 | const val SYSTEM_BAR_ALPHA = 0.9f |
| @@ -36,8 +34,8 @@ object ThemeHelper { | |||
| 36 | // Using a specific night mode check because this could apply incorrectly when using the | 34 | // Using a specific night mode check because this could apply incorrectly when using the |
| 37 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity | 35 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity |
| 38 | // will then show light mode colors/navigation bars but with black backgrounds. | 36 | // will then show light mode colors/navigation bars but with black backgrounds. |
| 39 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | 37 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && |
| 40 | && isNightMode(activity) | 38 | isNightMode(activity) |
| 41 | ) { | 39 | ) { |
| 42 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) | 40 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) |
| 43 | } | 41 | } |
| @@ -46,8 +44,10 @@ object ThemeHelper { | |||
| 46 | @ColorInt | 44 | @ColorInt |
| 47 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { | 45 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { |
| 48 | return Color.argb( | 46 | return Color.argb( |
| 49 | (alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color), | 47 | (alphaFactor * Color.alpha(color)).roundToInt(), |
| 50 | Color.green(color), Color.blue(color) | 48 | Color.red(color), |
| 49 | Color.green(color), | ||
| 50 | Color.blue(color) | ||
| 51 | ) | 51 | ) |
| 52 | } | 52 | } |
| 53 | 53 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt index d89a89983..685ccaa76 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt | |||
| @@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor( | |||
| 38 | newWidth = width | 38 | newWidth = width |
| 39 | newHeight = (width / aspectRatio).roundToInt() | 39 | newHeight = (width / aspectRatio).roundToInt() |
| 40 | } | 40 | } |
| 41 | val left = (width - newWidth) / 2; | 41 | val left = (width - newWidth) / 2 |
| 42 | val top = (height - newHeight) / 2; | 42 | val top = (height - newHeight) / 2 |
| 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) | 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) |
| 44 | } else { | 44 | } else { |
| 45 | setLeftTopRightBottom(0, 0, width, height) | 45 | setLeftTopRightBottom(0, 0, width, height) |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 4091c23d1..632aa50b3 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -202,6 +202,11 @@ public: | |||
| 202 | return m_is_running; | 202 | return m_is_running; |
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | bool IsPaused() const { | ||
| 206 | std::scoped_lock lock(m_mutex); | ||
| 207 | return m_is_running && m_is_paused; | ||
| 208 | } | ||
| 209 | |||
| 205 | const Core::PerfStatsResults& PerfStats() const { | 210 | const Core::PerfStatsResults& PerfStats() const { |
| 206 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); | 211 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
| 207 | return m_perf_stats; | 212 | return m_perf_stats; |
| @@ -287,11 +292,13 @@ public: | |||
| 287 | void PauseEmulation() { | 292 | void PauseEmulation() { |
| 288 | std::scoped_lock lock(m_mutex); | 293 | std::scoped_lock lock(m_mutex); |
| 289 | m_system.Pause(); | 294 | m_system.Pause(); |
| 295 | m_is_paused = true; | ||
| 290 | } | 296 | } |
| 291 | 297 | ||
| 292 | void UnPauseEmulation() { | 298 | void UnPauseEmulation() { |
| 293 | std::scoped_lock lock(m_mutex); | 299 | std::scoped_lock lock(m_mutex); |
| 294 | m_system.Run(); | 300 | m_system.Run(); |
| 301 | m_is_paused = false; | ||
| 295 | } | 302 | } |
| 296 | 303 | ||
| 297 | void HaltEmulation() { | 304 | void HaltEmulation() { |
| @@ -473,6 +480,7 @@ private: | |||
| 473 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; | 480 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; |
| 474 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | 481 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |
| 475 | bool m_is_running{}; | 482 | bool m_is_running{}; |
| 483 | bool m_is_paused{}; | ||
| 476 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; | 484 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |
| 477 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; | 485 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; |
| 478 | 486 | ||
| @@ -552,6 +560,26 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver( | |||
| 552 | GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir)); | 560 | GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir)); |
| 553 | } | 561 | } |
| 554 | 562 | ||
| 563 | [[maybe_unused]] static bool CheckKgslPresent() { | ||
| 564 | constexpr auto KgslPath{"/dev/kgsl-3d0"}; | ||
| 565 | |||
| 566 | return access(KgslPath, F_OK) == 0; | ||
| 567 | } | ||
| 568 | |||
| 569 | [[maybe_unused]] bool SupportsCustomDriver() { | ||
| 570 | return android_get_device_api_level() >= 28 && CheckKgslPresent(); | ||
| 571 | } | ||
| 572 | |||
| 573 | jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading( | ||
| 574 | [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject instance) { | ||
| 575 | #ifdef ARCHITECTURE_arm64 | ||
| 576 | // If the KGSL device exists custom drivers can be loaded using adrenotools | ||
| 577 | return SupportsCustomDriver(); | ||
| 578 | #else | ||
| 579 | return false; | ||
| 580 | #endif | ||
| 581 | } | ||
| 582 | |||
| 555 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, | 583 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, |
| 556 | [[maybe_unused]] jclass clazz) { | 584 | [[maybe_unused]] jclass clazz) { |
| 557 | Core::Crypto::KeyManager::Instance().ReloadKeys(); | 585 | Core::Crypto::KeyManager::Instance().ReloadKeys(); |
| @@ -583,6 +611,11 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv | |||
| 583 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); | 611 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); |
| 584 | } | 612 | } |
| 585 | 613 | ||
| 614 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env, | ||
| 615 | [[maybe_unused]] jclass clazz) { | ||
| 616 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); | ||
| 617 | } | ||
| 618 | |||
| 586 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, | 619 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, |
| 587 | [[maybe_unused]] jclass clazz) { | 620 | [[maybe_unused]] jclass clazz) { |
| 588 | return EmulationSession::GetInstance().IsHandheldOnly(); | 621 | return EmulationSession::GetInstance().IsHandheldOnly(); |
diff --git a/src/android/app/src/main/res/drawable/ic_pip_pause.xml b/src/android/app/src/main/res/drawable/ic_pip_pause.xml new file mode 100644 index 000000000..4a7d4ea03 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_pause.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportHeight="24" | ||
| 5 | android:viewportWidth="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="@android:color/white" | ||
| 8 | android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_pip_play.xml b/src/android/app/src/main/res/drawable/ic_pip_play.xml new file mode 100644 index 000000000..2303a4623 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_play.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportHeight="24" | ||
| 5 | android:viewportWidth="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="@android:color/white" | ||
| 8 | android:pathData="M8,5v14l11,-7z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml index f6360a65b..139065d3d 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml | |||
| @@ -1,13 +1,9 @@ | |||
| 1 | <FrameLayout | 1 | <androidx.fragment.app.FragmentContainerView |
| 2 | xmlns:android="http://schemas.android.com/apk/res/android" | 2 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | android:id="@+id/frame_content" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:id="@+id/fragment_container" | ||
| 5 | android:name="androidx.navigation.fragment.NavHostFragment" | ||
| 4 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 5 | android:layout_height="match_parent" | 7 | android:layout_height="match_parent" |
| 6 | android:keepScreenOn="true"> | 8 | android:keepScreenOn="true" |
| 7 | 9 | app:defaultNavHost="true" /> | |
| 8 | <FrameLayout | ||
| 9 | android:id="@+id/frame_emulation_fragment" | ||
| 10 | android:layout_width="match_parent" | ||
| 11 | android:layout_height="match_parent" /> | ||
| 12 | |||
| 13 | </FrameLayout> | ||
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 09b789b6b..e54a10e8f 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml | |||
| @@ -12,49 +12,65 @@ | |||
| 12 | android:layout_width="match_parent" | 12 | android:layout_width="match_parent" |
| 13 | android:layout_height="match_parent"> | 13 | android:layout_height="match_parent"> |
| 14 | 14 | ||
| 15 | <!-- This is what everything is rendered to during emulation --> | 15 | <FrameLayout |
| 16 | <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView | 16 | android:id="@+id/emulation_container" |
| 17 | android:id="@+id/surface_emulation" | ||
| 18 | android:layout_width="match_parent" | 17 | android:layout_width="match_parent" |
| 19 | android:layout_height="match_parent" | 18 | android:layout_height="match_parent"> |
| 20 | android:layout_gravity="center" | 19 | |
| 21 | android:focusable="false" | 20 | <!-- This is what everything is rendered to during emulation --> |
| 22 | android:focusableInTouchMode="false" /> | 21 | <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView |
| 22 | android:id="@+id/surface_emulation" | ||
| 23 | android:layout_width="match_parent" | ||
| 24 | android:layout_height="match_parent" | ||
| 25 | android:layout_gravity="center" | ||
| 26 | android:focusable="false" | ||
| 27 | android:focusableInTouchMode="false" /> | ||
| 28 | |||
| 29 | </FrameLayout> | ||
| 23 | 30 | ||
| 24 | <FrameLayout | 31 | <FrameLayout |
| 25 | android:id="@+id/overlay_container" | 32 | android:id="@+id/input_container" |
| 26 | android:layout_width="match_parent" | 33 | android:layout_width="match_parent" |
| 27 | android:layout_height="match_parent" | 34 | android:layout_height="match_parent" |
| 28 | android:layout_gravity="bottom"> | 35 | android:layout_gravity="bottom"> |
| 29 | 36 | ||
| 30 | <!-- This is the onscreen input overlay --> | 37 | <!-- This is the onscreen input overlay --> |
| 31 | <org.yuzu.yuzu_emu.overlay.InputOverlay | 38 | <org.yuzu.yuzu_emu.overlay.InputOverlay |
| 32 | android:id="@+id/surface_input_overlay" | 39 | android:id="@+id/surface_input_overlay" |
| 40 | android:layout_width="match_parent" | ||
| 41 | android:layout_height="match_parent" | ||
| 42 | android:layout_gravity="center" | ||
| 43 | android:focusable="true" | ||
| 44 | android:focusableInTouchMode="true" /> | ||
| 45 | |||
| 46 | <Button | ||
| 47 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 48 | android:id="@+id/done_control_config" | ||
| 49 | android:layout_width="wrap_content" | ||
| 50 | android:layout_height="wrap_content" | ||
| 51 | android:layout_gravity="center" | ||
| 52 | android:text="@string/emulation_done" | ||
| 53 | android:visibility="gone" /> | ||
| 54 | |||
| 55 | </FrameLayout> | ||
| 56 | |||
| 57 | <FrameLayout | ||
| 58 | android:id="@+id/overlay_container" | ||
| 33 | android:layout_width="match_parent" | 59 | android:layout_width="match_parent" |
| 34 | android:layout_height="match_parent" | 60 | android:layout_height="match_parent"> |
| 35 | android:focusable="true" | ||
| 36 | android:focusableInTouchMode="true" /> | ||
| 37 | 61 | ||
| 38 | <TextView | 62 | <TextView |
| 39 | android:id="@+id/show_fps_text" | 63 | android:id="@+id/show_fps_text" |
| 40 | android:layout_width="wrap_content" | 64 | android:layout_width="wrap_content" |
| 41 | android:layout_height="wrap_content" | 65 | android:layout_height="wrap_content" |
| 42 | android:layout_gravity="left" | 66 | android:layout_gravity="left" |
| 43 | android:clickable="false" | 67 | android:clickable="false" |
| 44 | android:focusable="false" | 68 | android:focusable="false" |
| 45 | android:shadowColor="@android:color/black" | 69 | android:shadowColor="@android:color/black" |
| 46 | android:textColor="@android:color/white" | 70 | android:textColor="@android:color/white" |
| 47 | android:textSize="12sp" | 71 | android:textSize="12sp" |
| 48 | tools:ignore="RtlHardcoded" /> | 72 | tools:ignore="RtlHardcoded" /> |
| 49 | 73 | ||
| 50 | <Button | ||
| 51 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 52 | android:id="@+id/done_control_config" | ||
| 53 | android:layout_width="wrap_content" | ||
| 54 | android:layout_height="wrap_content" | ||
| 55 | android:layout_gravity="center" | ||
| 56 | android:text="@string/emulation_done" | ||
| 57 | android:visibility="gone" /> | ||
| 58 | </FrameLayout> | 74 | </FrameLayout> |
| 59 | 75 | ||
| 60 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | 76 | </androidx.coordinatorlayout.widget.CoordinatorLayout> |
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml index 599d845ad..a5767adee 100644 --- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml +++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml | |||
| @@ -1,16 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 3 | xmlns:tools="http://schemas.android.com/tools" | 4 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:layout_width="match_parent" | 5 | android:layout_width="match_parent" |
| 5 | android:layout_height="wrap_content" | 6 | android:layout_height="wrap_content" |
| 6 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 7 | android:background="?android:attr/selectableItemBackground" | 7 | android:background="?android:attr/selectableItemBackground" |
| 8 | android:clickable="true" | 8 | android:clickable="true" |
| 9 | android:focusable="true" | 9 | android:focusable="true" |
| 10 | android:minHeight="72dp" | 10 | android:minHeight="72dp" |
| 11 | android:paddingVertical="@dimen/spacing_large" | ||
| 11 | android:paddingStart="@dimen/spacing_large" | 12 | android:paddingStart="@dimen/spacing_large" |
| 12 | android:paddingEnd="24dp" | 13 | android:paddingEnd="24dp"> |
| 13 | android:paddingVertical="@dimen/spacing_large"> | ||
| 14 | 14 | ||
| 15 | <com.google.android.material.materialswitch.MaterialSwitch | 15 | <com.google.android.material.materialswitch.MaterialSwitch |
| 16 | android:id="@+id/switch_widget" | 16 | android:id="@+id/switch_widget" |
| @@ -19,32 +19,35 @@ | |||
| 19 | android:layout_alignParentEnd="true" | 19 | android:layout_alignParentEnd="true" |
| 20 | android:layout_centerVertical="true" /> | 20 | android:layout_centerVertical="true" /> |
| 21 | 21 | ||
| 22 | <com.google.android.material.textview.MaterialTextView | 22 | <LinearLayout |
| 23 | style="@style/TextAppearance.Material3.BodySmall" | 23 | android:layout_width="match_parent" |
| 24 | android:id="@+id/text_setting_description" | ||
| 25 | android:layout_width="wrap_content" | ||
| 26 | android:layout_height="wrap_content" | ||
| 27 | android:layout_alignParentStart="true" | ||
| 28 | android:layout_alignStart="@+id/text_setting_name" | ||
| 29 | android:layout_below="@+id/text_setting_name" | ||
| 30 | android:layout_marginEnd="@dimen/spacing_large" | ||
| 31 | android:layout_marginTop="@dimen/spacing_small" | ||
| 32 | android:layout_toStartOf="@+id/switch_widget" | ||
| 33 | android:textAlignment="viewStart" | ||
| 34 | tools:text="@string/frame_limit_enable_description" /> | ||
| 35 | |||
| 36 | <com.google.android.material.textview.MaterialTextView | ||
| 37 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
| 38 | android:id="@+id/text_setting_name" | ||
| 39 | android:layout_width="0dp" | ||
| 40 | android:layout_height="wrap_content" | 24 | android:layout_height="wrap_content" |
| 41 | android:layout_alignParentStart="true" | ||
| 42 | android:layout_alignParentTop="true" | 25 | android:layout_alignParentTop="true" |
| 26 | android:layout_centerVertical="true" | ||
| 43 | android:layout_marginEnd="@dimen/spacing_large" | 27 | android:layout_marginEnd="@dimen/spacing_large" |
| 44 | android:layout_toStartOf="@+id/switch_widget" | 28 | android:layout_toStartOf="@+id/switch_widget" |
| 45 | android:textSize="16sp" | 29 | android:gravity="center_vertical" |
| 46 | android:textAlignment="viewStart" | 30 | android:orientation="vertical"> |
| 47 | app:lineHeight="28dp" | 31 | |
| 48 | tools:text="@string/frame_limit_enable" /> | 32 | <com.google.android.material.textview.MaterialTextView |
| 33 | android:id="@+id/text_setting_name" | ||
| 34 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
| 35 | android:layout_width="wrap_content" | ||
| 36 | android:layout_height="wrap_content" | ||
| 37 | android:textAlignment="viewStart" | ||
| 38 | android:textSize="16sp" | ||
| 39 | app:lineHeight="28dp" | ||
| 40 | tools:text="@string/frame_limit_enable" /> | ||
| 41 | |||
| 42 | <com.google.android.material.textview.MaterialTextView | ||
| 43 | android:id="@+id/text_setting_description" | ||
| 44 | style="@style/TextAppearance.Material3.BodySmall" | ||
| 45 | android:layout_width="wrap_content" | ||
| 46 | android:layout_height="wrap_content" | ||
| 47 | android:layout_marginTop="@dimen/spacing_small" | ||
| 48 | android:textAlignment="viewStart" | ||
| 49 | tools:text="@string/frame_limit_enable_description" /> | ||
| 50 | |||
| 51 | </LinearLayout> | ||
| 49 | 52 | ||
| 50 | </RelativeLayout> | 53 | </RelativeLayout> |
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml index abd24df6f..cf85bc0da 100644 --- a/src/android/app/src/main/res/layout/list_item_settings_header.xml +++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml | |||
| @@ -1,20 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:tools="http://schemas.android.com/tools" | 3 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:id="@+id/text_header_name" | ||
| 5 | style="@style/TextAppearance.Material3.TitleSmall" | ||
| 4 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 5 | android:layout_height="48dp" | 7 | android:layout_height="wrap_content" |
| 6 | android:paddingVertical="4dp" | 8 | android:layout_gravity="start|center_vertical" |
| 7 | android:paddingHorizontal="@dimen/spacing_large"> | 9 | android:paddingHorizontal="@dimen/spacing_large" |
| 8 | 10 | android:paddingVertical="16dp" | |
| 9 | <com.google.android.material.textview.MaterialTextView | 11 | android:textAlignment="viewStart" |
| 10 | style="@style/TextAppearance.Material3.TitleSmall" | 12 | android:textColor="?attr/colorPrimary" |
| 11 | android:id="@+id/text_header_name" | 13 | android:textStyle="bold" |
| 12 | android:layout_width="match_parent" | 14 | tools:text="CPU Settings" /> |
| 13 | android:layout_height="wrap_content" | ||
| 14 | android:layout_gravity="start|center_vertical" | ||
| 15 | android:textColor="?attr/colorPrimary" | ||
| 16 | android:textAlignment="viewStart" | ||
| 17 | android:textStyle="bold" | ||
| 18 | tools:text="CPU Settings" /> | ||
| 19 | |||
| 20 | </FrameLayout> | ||
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml new file mode 100644 index 000000000..8208f4c2c --- /dev/null +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <navigation xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | android:id="@+id/emulation_navigation" | ||
| 6 | app:startDestination="@id/emulationFragment"> | ||
| 7 | |||
| 8 | <fragment | ||
| 9 | android:id="@+id/emulationFragment" | ||
| 10 | android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment" | ||
| 11 | android:label="fragment_emulation" | ||
| 12 | tools:layout="@layout/fragment_emulation" > | ||
| 13 | <argument | ||
| 14 | android:name="game" | ||
| 15 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | ||
| 16 | </fragment> | ||
| 17 | |||
| 18 | </navigation> | ||
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 48072683e..fcebba726 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -56,4 +56,18 @@ | |||
| 56 | android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" | 56 | android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" |
| 57 | android:label="LicensesFragment" /> | 57 | android:label="LicensesFragment" /> |
| 58 | 58 | ||
| 59 | <activity | ||
| 60 | android:id="@+id/emulationActivity" | ||
| 61 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | ||
| 62 | android:label="EmulationActivity"> | ||
| 63 | <argument | ||
| 64 | android:name="game" | ||
| 65 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | ||
| 66 | </activity> | ||
| 67 | |||
| 68 | <action | ||
| 69 | android:id="@+id/action_global_emulationActivity" | ||
| 70 | app:destination="@id/emulationActivity" | ||
| 71 | app:launchSingleTop="true" /> | ||
| 72 | |||
| 59 | </navigation> | 73 | </navigation> |
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index ea20cb17c..6d092f7a9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml | |||
| @@ -119,6 +119,18 @@ | |||
| 119 | <item>3</item> | 119 | <item>3</item> |
| 120 | </integer-array> | 120 | </integer-array> |
| 121 | 121 | ||
| 122 | <string-array name="rendererScreenLayoutNames"> | ||
| 123 | <item>@string/screen_layout_landscape</item> | ||
| 124 | <item>@string/screen_layout_portrait</item> | ||
| 125 | <item>@string/screen_layout_auto</item> | ||
| 126 | </string-array> | ||
| 127 | |||
| 128 | <integer-array name="rendererScreenLayoutValues"> | ||
| 129 | <item>5</item> | ||
| 130 | <item>4</item> | ||
| 131 | <item>0</item> | ||
| 132 | </integer-array> | ||
| 133 | |||
| 122 | <string-array name="rendererAspectRatioNames"> | 134 | <string-array name="rendererAspectRatioNames"> |
| 123 | <item>@string/ratio_default</item> | 135 | <item>@string/ratio_default</item> |
| 124 | <item>@string/ratio_force_four_three</item> | 136 | <item>@string/ratio_force_four_three</item> |
| @@ -224,4 +236,15 @@ | |||
| 224 | <item>2</item> | 236 | <item>2</item> |
| 225 | </integer-array> | 237 | </integer-array> |
| 226 | 238 | ||
| 239 | <string-array name="outputEngineEntries"> | ||
| 240 | <item>@string/auto</item> | ||
| 241 | <item>@string/cubeb</item> | ||
| 242 | <item>@string/string_null</item> | ||
| 243 | </string-array> | ||
| 244 | <string-array name="outputEngineValues"> | ||
| 245 | <item>auto</item> | ||
| 246 | <item>cubeb</item> | ||
| 247 | <item>null</item> | ||
| 248 | </string-array> | ||
| 249 | |||
| 227 | </resources> | 250 | </resources> |
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index bc614b81d..2e93b408c 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml | |||
| @@ -34,4 +34,68 @@ | |||
| 34 | <integer name="SWITCH_BUTTON_DPAD_X">260</integer> | 34 | <integer name="SWITCH_BUTTON_DPAD_X">260</integer> |
| 35 | <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> | 35 | <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> |
| 36 | 36 | ||
| 37 | <!-- Default SWITCH portrait layout --> | ||
| 38 | <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> | ||
| 39 | <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer> | ||
| 40 | <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer> | ||
| 41 | <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer> | ||
| 42 | <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer> | ||
| 43 | <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer> | ||
| 44 | <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer> | ||
| 45 | <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer> | ||
| 46 | <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer> | ||
| 47 | <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer> | ||
| 48 | <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer> | ||
| 49 | <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer> | ||
| 50 | <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer> | ||
| 51 | <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer> | ||
| 52 | <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer> | ||
| 53 | <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer> | ||
| 54 | <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer> | ||
| 55 | <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer> | ||
| 56 | <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer> | ||
| 57 | <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer> | ||
| 58 | <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer> | ||
| 59 | <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer> | ||
| 60 | <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer> | ||
| 61 | <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer> | ||
| 62 | <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer> | ||
| 63 | <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer> | ||
| 64 | <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer> | ||
| 65 | <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> | ||
| 66 | <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> | ||
| 67 | <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> | ||
| 68 | |||
| 69 | <!-- Default SWITCH foldable layout --> | ||
| 70 | <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> | ||
| 71 | <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer> | ||
| 72 | <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer> | ||
| 73 | <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer> | ||
| 74 | <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer> | ||
| 75 | <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer> | ||
| 76 | <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer> | ||
| 77 | <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer> | ||
| 78 | <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer> | ||
| 79 | <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer> | ||
| 80 | <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer> | ||
| 81 | <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer> | ||
| 82 | <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer> | ||
| 83 | <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer> | ||
| 84 | <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer> | ||
| 85 | <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer> | ||
| 86 | <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer> | ||
| 87 | <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer> | ||
| 88 | <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer> | ||
| 89 | <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer> | ||
| 90 | <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer> | ||
| 91 | <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer> | ||
| 92 | <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer> | ||
| 93 | <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer> | ||
| 94 | <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer> | ||
| 95 | <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer> | ||
| 96 | <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer> | ||
| 97 | <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> | ||
| 98 | <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> | ||
| 99 | <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> | ||
| 100 | |||
| 37 | </resources> | 101 | </resources> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c236811fa..cc1d8c39d 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <resources> | 2 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> |
| 3 | 3 | ||
| 4 | <!-- General application strings --> | 4 | <!-- General application strings --> |
| 5 | <string name="app_name" translatable="false">yuzu</string> | 5 | <string name="app_name" translatable="false">yuzu</string> |
| @@ -158,10 +158,10 @@ | |||
| 158 | <string name="set_custom_rtc">Set custom RTC</string> | 158 | <string name="set_custom_rtc">Set custom RTC</string> |
| 159 | 159 | ||
| 160 | <!-- Graphics settings strings --> | 160 | <!-- Graphics settings strings --> |
| 161 | <string name="renderer_api">API</string> | ||
| 162 | <string name="renderer_accuracy">Accuracy level</string> | 161 | <string name="renderer_accuracy">Accuracy level</string> |
| 163 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> | 162 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> |
| 164 | <string name="renderer_vsync">VSync mode</string> | 163 | <string name="renderer_vsync">VSync mode</string> |
| 164 | <string name="renderer_screen_layout">Orientation</string> | ||
| 165 | <string name="renderer_aspect_ratio">Aspect ratio</string> | 165 | <string name="renderer_aspect_ratio">Aspect ratio</string> |
| 166 | <string name="renderer_scaling_filter">Window adapting filter</string> | 166 | <string name="renderer_scaling_filter">Window adapting filter</string> |
| 167 | <string name="renderer_anti_aliasing">Anti-aliasing method</string> | 167 | <string name="renderer_anti_aliasing">Anti-aliasing method</string> |
| @@ -171,12 +171,21 @@ | |||
| 171 | <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string> | 171 | <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string> |
| 172 | <string name="renderer_reactive_flushing">Use reactive flushing</string> | 172 | <string name="renderer_reactive_flushing">Use reactive flushing</string> |
| 173 | <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> | 173 | <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> |
| 174 | <string name="renderer_debug">Graphics debugging</string> | ||
| 175 | <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> | ||
| 176 | <string name="use_disk_shader_cache">Disk shader cache</string> | 174 | <string name="use_disk_shader_cache">Disk shader cache</string> |
| 177 | <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> | 175 | <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> |
| 178 | 176 | ||
| 177 | <!-- Debug settings strings --> | ||
| 178 | <string name="cpu">CPU</string> | ||
| 179 | <string name="cpu_debug_mode">CPU Debugging</string> | ||
| 180 | <string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string> | ||
| 181 | <string name="gpu">GPU</string> | ||
| 182 | <string name="renderer_api">API</string> | ||
| 183 | <string name="renderer_debug">Graphics debugging</string> | ||
| 184 | <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> | ||
| 185 | <string name="fastmem">Fastmem</string> | ||
| 186 | |||
| 179 | <!-- Audio settings strings --> | 187 | <!-- Audio settings strings --> |
| 188 | <string name="audio_output_engine">Output engine</string> | ||
| 180 | <string name="audio_volume">Volume</string> | 189 | <string name="audio_volume">Volume</string> |
| 181 | <string name="audio_volume_description">Specifies the volume of audio output.</string> | 190 | <string name="audio_volume_description">Specifies the volume of audio output.</string> |
| 182 | 191 | ||
| @@ -195,6 +204,7 @@ | |||
| 195 | <string name="learn_more">Learn more</string> | 204 | <string name="learn_more">Learn more</string> |
| 196 | <string name="auto">Auto</string> | 205 | <string name="auto">Auto</string> |
| 197 | <string name="submit">Submit</string> | 206 | <string name="submit">Submit</string> |
| 207 | <string name="string_null">Null</string> | ||
| 198 | 208 | ||
| 199 | <!-- GPU driver installation --> | 209 | <!-- GPU driver installation --> |
| 200 | <string name="select_gpu_driver">Select GPU driver</string> | 210 | <string name="select_gpu_driver">Select GPU driver</string> |
| @@ -326,6 +336,11 @@ | |||
| 326 | <string name="anti_aliasing_fxaa">FXAA</string> | 336 | <string name="anti_aliasing_fxaa">FXAA</string> |
| 327 | <string name="anti_aliasing_smaa">SMAA</string> | 337 | <string name="anti_aliasing_smaa">SMAA</string> |
| 328 | 338 | ||
| 339 | <!-- Screen Layouts --> | ||
| 340 | <string name="screen_layout_landscape">Landscape</string> | ||
| 341 | <string name="screen_layout_portrait">Portrait</string> | ||
| 342 | <string name="screen_layout_auto">Auto</string> | ||
| 343 | |||
| 329 | <!-- Aspect Ratios --> | 344 | <!-- Aspect Ratios --> |
| 330 | <string name="ratio_default">Default (16:9)</string> | 345 | <string name="ratio_default">Default (16:9)</string> |
| 331 | <string name="ratio_force_four_three">Force 4:3</string> | 346 | <string name="ratio_force_four_three">Force 4:3</string> |
| @@ -360,10 +375,19 @@ | |||
| 360 | <string name="theme_mode_light">Light</string> | 375 | <string name="theme_mode_light">Light</string> |
| 361 | <string name="theme_mode_dark">Dark</string> | 376 | <string name="theme_mode_dark">Dark</string> |
| 362 | 377 | ||
| 378 | <!-- Audio output engines --> | ||
| 379 | <string name="cubeb">cubeb</string> | ||
| 380 | |||
| 363 | <!-- Black backgrounds theme --> | 381 | <!-- Black backgrounds theme --> |
| 364 | <string name="use_black_backgrounds">Black backgrounds</string> | 382 | <string name="use_black_backgrounds">Black backgrounds</string> |
| 365 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> | 383 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> |
| 366 | 384 | ||
| 385 | <!-- Picture-In-Picture --> | ||
| 386 | <string name="picture_in_picture">Picture in Picture</string> | ||
| 387 | <string name="picture_in_picture_description">Minimize window when placed in the background</string> | ||
| 388 | <string name="pause">Pause</string> | ||
| 389 | <string name="play">Play</string> | ||
| 390 | |||
| 367 | <!-- Licenses screen strings --> | 391 | <!-- Licenses screen strings --> |
| 368 | <string name="licenses">Licenses</string> | 392 | <string name="licenses">Licenses</string> |
| 369 | <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> | 393 | <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> |
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts index e19e8ce58..80f370c16 100644 --- a/src/android/build.gradle.kts +++ b/src/android/build.gradle.kts | |||
| @@ -11,3 +11,12 @@ plugins { | |||
| 11 | tasks.register("clean").configure { | 11 | tasks.register("clean").configure { |
| 12 | delete(rootProject.buildDir) | 12 | delete(rootProject.buildDir) |
| 13 | } | 13 | } |
| 14 | |||
| 15 | buildscript { | ||
| 16 | repositories { | ||
| 17 | google() | ||
| 18 | } | ||
| 19 | dependencies { | ||
| 20 | classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0") | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index e1716c62d..6d66c926d 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp | |||
| @@ -3,6 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #include "common/fs/file.h" | 4 | #include "common/fs/file.h" |
| 5 | #include "common/fs/fs.h" | 5 | #include "common/fs/fs.h" |
| 6 | #ifdef ANDROID | ||
| 7 | #include "common/fs/fs_android.h" | ||
| 8 | #endif | ||
| 6 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 7 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 8 | 11 | ||
| @@ -525,15 +528,39 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 525 | // Generic Filesystem Operations | 528 | // Generic Filesystem Operations |
| 526 | 529 | ||
| 527 | bool Exists(const fs::path& path) { | 530 | bool Exists(const fs::path& path) { |
| 531 | #ifdef ANDROID | ||
| 532 | if (Android::IsContentUri(path)) { | ||
| 533 | return Android::Exists(path); | ||
| 534 | } else { | ||
| 535 | return fs::exists(path); | ||
| 536 | } | ||
| 537 | #else | ||
| 528 | return fs::exists(path); | 538 | return fs::exists(path); |
| 539 | #endif | ||
| 529 | } | 540 | } |
| 530 | 541 | ||
| 531 | bool IsFile(const fs::path& path) { | 542 | bool IsFile(const fs::path& path) { |
| 543 | #ifdef ANDROID | ||
| 544 | if (Android::IsContentUri(path)) { | ||
| 545 | return !Android::IsDirectory(path); | ||
| 546 | } else { | ||
| 547 | return fs::is_regular_file(path); | ||
| 548 | } | ||
| 549 | #else | ||
| 532 | return fs::is_regular_file(path); | 550 | return fs::is_regular_file(path); |
| 551 | #endif | ||
| 533 | } | 552 | } |
| 534 | 553 | ||
| 535 | bool IsDir(const fs::path& path) { | 554 | bool IsDir(const fs::path& path) { |
| 555 | #ifdef ANDROID | ||
| 556 | if (Android::IsContentUri(path)) { | ||
| 557 | return Android::IsDirectory(path); | ||
| 558 | } else { | ||
| 559 | return fs::is_directory(path); | ||
| 560 | } | ||
| 561 | #else | ||
| 536 | return fs::is_directory(path); | 562 | return fs::is_directory(path); |
| 563 | #endif | ||
| 537 | } | 564 | } |
| 538 | 565 | ||
| 539 | fs::path GetCurrentDir() { | 566 | fs::path GetCurrentDir() { |
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index bb8a52648..b441c2a12 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h | |||
| @@ -12,7 +12,10 @@ | |||
| 12 | "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") | 12 | "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") |
| 13 | 13 | ||
| 14 | #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ | 14 | #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ |
| 15 | V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") | 15 | V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \ |
| 16 | V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \ | ||
| 17 | "(Ljava/lang/String;)Z") \ | ||
| 18 | V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") | ||
| 16 | 19 | ||
| 17 | namespace Common::FS::Android { | 20 | namespace Common::FS::Android { |
| 18 | 21 | ||
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp index ceb0b41c6..7c17bbefa 100644 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp | |||
| @@ -15,7 +15,7 @@ namespace FileSys::SystemArchive { | |||
| 15 | const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&> | 15 | const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&> |
| 16 | tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, | 16 | tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, |
| 17 | {"America", NxTzdb::america}, | 17 | {"America", NxTzdb::america}, |
| 18 | {"Antartica", NxTzdb::antartica}, | 18 | {"Antarctica", NxTzdb::antarctica}, |
| 19 | {"Arctic", NxTzdb::arctic}, | 19 | {"Arctic", NxTzdb::arctic}, |
| 20 | {"Asia", NxTzdb::asia}, | 20 | {"Asia", NxTzdb::asia}, |
| 21 | {"Atlantic", NxTzdb::atlantic}, | 21 | {"Atlantic", NxTzdb::atlantic}, |
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index 853b893a1..311a59e5f 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp | |||
| @@ -150,23 +150,29 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t | |||
| 150 | while (cur_length > 0 && it != concatenation_map.end()) { | 150 | while (cur_length > 0 && it != concatenation_map.end()) { |
| 151 | // Check if we can read the file at this position. | 151 | // Check if we can read the file at this position. |
| 152 | const auto& file = it->file; | 152 | const auto& file = it->file; |
| 153 | const u64 file_offset = it->offset; | 153 | const u64 map_offset = it->offset; |
| 154 | const u64 file_size = file->GetSize(); | 154 | const u64 file_size = file->GetSize(); |
| 155 | 155 | ||
| 156 | if (cur_offset >= file_offset + file_size) { | 156 | if (cur_offset > map_offset + file_size) { |
| 157 | // Entirely out of bounds read. | 157 | // Entirely out of bounds read. |
| 158 | break; | 158 | break; |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | // Read the file at this position. | 161 | // Read the file at this position. |
| 162 | const u64 intended_read_size = std::min<u64>(cur_length, file_size); | 162 | const u64 file_seek = cur_offset - map_offset; |
| 163 | const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek); | ||
| 163 | const u64 actual_read_size = | 164 | const u64 actual_read_size = |
| 164 | file->Read(data + (cur_offset - offset), intended_read_size, cur_offset - file_offset); | 165 | file->Read(data + (cur_offset - offset), intended_read_size, file_seek); |
| 165 | 166 | ||
| 166 | // Update tracking. | 167 | // Update tracking. |
| 167 | cur_offset += actual_read_size; | 168 | cur_offset += actual_read_size; |
| 168 | cur_length -= actual_read_size; | 169 | cur_length -= actual_read_size; |
| 169 | it++; | 170 | it++; |
| 171 | |||
| 172 | // If we encountered a short read, we're done. | ||
| 173 | if (actual_read_size < intended_read_size) { | ||
| 174 | break; | ||
| 175 | } | ||
| 170 | } | 176 | } |
| 171 | 177 | ||
| 172 | return cur_offset - offset; | 178 | return cur_offset - offset; |
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index cc0076238..7a15d8438 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp | |||
| @@ -25,6 +25,8 @@ namespace FS = Common::FS; | |||
| 25 | 25 | ||
| 26 | namespace { | 26 | namespace { |
| 27 | 27 | ||
| 28 | constexpr size_t MaxOpenFiles = 512; | ||
| 29 | |||
| 28 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { | 30 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { |
| 29 | switch (mode) { | 31 | switch (mode) { |
| 30 | case Mode::Read: | 32 | case Mode::Read: |
| @@ -73,28 +75,30 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { | |||
| 73 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | 75 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { |
| 74 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 76 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 75 | 77 | ||
| 76 | if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { | 78 | if (auto it = cache.find(path); it != cache.end()) { |
| 77 | const auto& weak = weak_iter->second; | 79 | if (auto file = it->second.lock(); file) { |
| 78 | 80 | return file; | |
| 79 | if (!weak.expired()) { | ||
| 80 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); | ||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); | 84 | if (!FS::Exists(path) || !FS::IsFile(path)) { |
| 85 | |||
| 86 | if (!backing) { | ||
| 87 | return nullptr; | 85 | return nullptr; |
| 88 | } | 86 | } |
| 89 | 87 | ||
| 90 | cache.insert_or_assign(path, std::move(backing)); | 88 | auto reference = std::make_unique<FileReference>(); |
| 89 | this->InsertReferenceIntoList(*reference); | ||
| 91 | 90 | ||
| 92 | // Cannot use make_shared as RealVfsFile constructor is private | 91 | auto file = |
| 93 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); | 92 | std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); |
| 93 | cache[path] = file; | ||
| 94 | |||
| 95 | return file; | ||
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 98 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { |
| 97 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 99 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 100 | cache.erase(path); | ||
| 101 | |||
| 98 | // Current usages of CreateFile expect to delete the contents of an existing file. | 102 | // Current usages of CreateFile expect to delete the contents of an existing file. |
| 99 | if (FS::IsFile(path)) { | 103 | if (FS::IsFile(path)) { |
| 100 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; | 104 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; |
| @@ -123,51 +127,22 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_ | |||
| 123 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { | 127 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { |
| 124 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 128 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); |
| 125 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 129 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); |
| 126 | const auto cached_file_iter = cache.find(old_path); | 130 | cache.erase(old_path); |
| 127 | 131 | cache.erase(new_path); | |
| 128 | if (cached_file_iter != cache.cend()) { | 132 | if (!FS::RenameFile(old_path, new_path)) { |
| 129 | auto file = cached_file_iter->second.lock(); | ||
| 130 | |||
| 131 | if (!cached_file_iter->second.expired()) { | ||
| 132 | file->Close(); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!FS::RenameFile(old_path, new_path)) { | ||
| 136 | return nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | cache.erase(old_path); | ||
| 140 | file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | ||
| 141 | if (file->IsOpen()) { | ||
| 142 | cache.insert_or_assign(new_path, std::move(file)); | ||
| 143 | } else { | ||
| 144 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); | ||
| 145 | } | ||
| 146 | } else { | ||
| 147 | ASSERT(false); | ||
| 148 | return nullptr; | 133 | return nullptr; |
| 149 | } | 134 | } |
| 150 | |||
| 151 | return OpenFile(new_path, Mode::ReadWrite); | 135 | return OpenFile(new_path, Mode::ReadWrite); |
| 152 | } | 136 | } |
| 153 | 137 | ||
| 154 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { | 138 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { |
| 155 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 139 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 156 | const auto cached_iter = cache.find(path); | 140 | cache.erase(path); |
| 157 | |||
| 158 | if (cached_iter != cache.cend()) { | ||
| 159 | if (!cached_iter->second.expired()) { | ||
| 160 | cached_iter->second.lock()->Close(); | ||
| 161 | } | ||
| 162 | cache.erase(path); | ||
| 163 | } | ||
| 164 | |||
| 165 | return FS::RemoveFile(path); | 141 | return FS::RemoveFile(path); |
| 166 | } | 142 | } |
| 167 | 143 | ||
| 168 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { | 144 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { |
| 169 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 145 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 170 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 171 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 146 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 172 | } | 147 | } |
| 173 | 148 | ||
| @@ -176,7 +151,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms | |||
| 176 | if (!FS::CreateDirs(path)) { | 151 | if (!FS::CreateDirs(path)) { |
| 177 | return nullptr; | 152 | return nullptr; |
| 178 | } | 153 | } |
| 179 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 180 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 154 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 181 | } | 155 | } |
| 182 | 156 | ||
| @@ -194,73 +168,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 194 | if (!FS::RenameDir(old_path, new_path)) { | 168 | if (!FS::RenameDir(old_path, new_path)) { |
| 195 | return nullptr; | 169 | return nullptr; |
| 196 | } | 170 | } |
| 171 | return OpenDirectory(new_path, Mode::ReadWrite); | ||
| 172 | } | ||
| 197 | 173 | ||
| 198 | for (auto& kv : cache) { | 174 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { |
| 199 | // If the path in the cache doesn't start with old_path, then bail on this file. | 175 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 200 | if (kv.first.rfind(old_path, 0) != 0) { | 176 | return FS::RemoveDirRecursively(path); |
| 201 | continue; | 177 | } |
| 202 | } | ||
| 203 | 178 | ||
| 204 | const auto file_old_path = | 179 | void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms, |
| 205 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); | 180 | FileReference& reference) { |
| 206 | auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), | 181 | // Temporarily remove from list. |
| 207 | FS::DirectorySeparator::PlatformDefault); | 182 | this->RemoveReferenceFromList(reference); |
| 208 | const auto& cached = cache[file_old_path]; | ||
| 209 | 183 | ||
| 210 | if (cached.expired()) { | 184 | // Restore file if needed. |
| 211 | continue; | 185 | if (!reference.file) { |
| 212 | } | 186 | this->EvictSingleReference(); |
| 213 | 187 | ||
| 214 | auto file = cached.lock(); | 188 | reference.file = |
| 215 | cache.erase(file_old_path); | 189 | FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 216 | file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | 190 | if (reference.file) { |
| 217 | if (file->IsOpen()) { | 191 | num_open_files++; |
| 218 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); | ||
| 219 | } else { | ||
| 220 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); | ||
| 221 | } | 192 | } |
| 222 | } | 193 | } |
| 223 | 194 | ||
| 224 | return OpenDirectory(new_path, Mode::ReadWrite); | 195 | // Reinsert into list. |
| 196 | this->InsertReferenceIntoList(reference); | ||
| 225 | } | 197 | } |
| 226 | 198 | ||
| 227 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | 199 | void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { |
| 228 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 200 | // Remove from list. |
| 201 | this->RemoveReferenceFromList(*reference); | ||
| 229 | 202 | ||
| 230 | for (auto& kv : cache) { | 203 | // Close the file. |
| 231 | // If the path in the cache doesn't start with path, then bail on this file. | 204 | if (reference->file) { |
| 232 | if (kv.first.rfind(path, 0) != 0) { | 205 | reference->file.reset(); |
| 233 | continue; | 206 | num_open_files--; |
| 234 | } | 207 | } |
| 208 | } | ||
| 235 | 209 | ||
| 236 | const auto& entry = cache[kv.first]; | 210 | void RealVfsFilesystem::EvictSingleReference() { |
| 237 | if (!entry.expired()) { | 211 | if (num_open_files < MaxOpenFiles || open_references.empty()) { |
| 238 | entry.lock()->Close(); | 212 | return; |
| 239 | } | 213 | } |
| 214 | |||
| 215 | // Get and remove from list. | ||
| 216 | auto& reference = open_references.back(); | ||
| 217 | this->RemoveReferenceFromList(reference); | ||
| 240 | 218 | ||
| 241 | cache.erase(kv.first); | 219 | // Close the file. |
| 220 | if (reference.file) { | ||
| 221 | reference.file.reset(); | ||
| 222 | num_open_files--; | ||
| 242 | } | 223 | } |
| 243 | 224 | ||
| 244 | return FS::RemoveDirRecursively(path); | 225 | // Reinsert into closed list. |
| 226 | this->InsertReferenceIntoList(reference); | ||
| 227 | } | ||
| 228 | |||
| 229 | void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) { | ||
| 230 | if (reference.file) { | ||
| 231 | open_references.push_front(reference); | ||
| 232 | } else { | ||
| 233 | closed_references.push_front(reference); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { | ||
| 238 | if (reference.file) { | ||
| 239 | open_references.erase(open_references.iterator_to(reference)); | ||
| 240 | } else { | ||
| 241 | closed_references.erase(closed_references.iterator_to(reference)); | ||
| 242 | } | ||
| 245 | } | 243 | } |
| 246 | 244 | ||
| 247 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, | 245 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, |
| 248 | const std::string& path_, Mode perms_) | 246 | const std::string& path_, Mode perms_) |
| 249 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), | 247 | : base(base_), reference(std::move(reference_)), path(path_), |
| 250 | path_components(FS::SplitPathComponents(path_)), perms(perms_) {} | 248 | parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), |
| 249 | perms(perms_) {} | ||
| 251 | 250 | ||
| 252 | RealVfsFile::~RealVfsFile() = default; | 251 | RealVfsFile::~RealVfsFile() { |
| 252 | base.DropReference(std::move(reference)); | ||
| 253 | } | ||
| 253 | 254 | ||
| 254 | std::string RealVfsFile::GetName() const { | 255 | std::string RealVfsFile::GetName() const { |
| 255 | return path_components.back(); | 256 | return path_components.back(); |
| 256 | } | 257 | } |
| 257 | 258 | ||
| 258 | std::size_t RealVfsFile::GetSize() const { | 259 | std::size_t RealVfsFile::GetSize() const { |
| 259 | return backing->GetSize(); | 260 | base.RefreshReference(path, perms, *reference); |
| 261 | return reference->file ? reference->file->GetSize() : 0; | ||
| 260 | } | 262 | } |
| 261 | 263 | ||
| 262 | bool RealVfsFile::Resize(std::size_t new_size) { | 264 | bool RealVfsFile::Resize(std::size_t new_size) { |
| 263 | return backing->SetSize(new_size); | 265 | base.RefreshReference(path, perms, *reference); |
| 266 | return reference->file ? reference->file->SetSize(new_size) : false; | ||
| 264 | } | 267 | } |
| 265 | 268 | ||
| 266 | VirtualDir RealVfsFile::GetContainingDirectory() const { | 269 | VirtualDir RealVfsFile::GetContainingDirectory() const { |
| @@ -276,27 +279,25 @@ bool RealVfsFile::IsReadable() const { | |||
| 276 | } | 279 | } |
| 277 | 280 | ||
| 278 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 281 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { |
| 279 | if (!backing->Seek(static_cast<s64>(offset))) { | 282 | base.RefreshReference(path, perms, *reference); |
| 283 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 280 | return 0; | 284 | return 0; |
| 281 | } | 285 | } |
| 282 | return backing->ReadSpan(std::span{data, length}); | 286 | return reference->file->ReadSpan(std::span{data, length}); |
| 283 | } | 287 | } |
| 284 | 288 | ||
| 285 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 289 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { |
| 286 | if (!backing->Seek(static_cast<s64>(offset))) { | 290 | base.RefreshReference(path, perms, *reference); |
| 291 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 287 | return 0; | 292 | return 0; |
| 288 | } | 293 | } |
| 289 | return backing->WriteSpan(std::span{data, length}); | 294 | return reference->file->WriteSpan(std::span{data, length}); |
| 290 | } | 295 | } |
| 291 | 296 | ||
| 292 | bool RealVfsFile::Rename(std::string_view name) { | 297 | bool RealVfsFile::Rename(std::string_view name) { |
| 293 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; | 298 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; |
| 294 | } | 299 | } |
| 295 | 300 | ||
| 296 | void RealVfsFile::Close() { | ||
| 297 | backing->Close(); | ||
| 298 | } | ||
| 299 | |||
| 300 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if | 301 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if |
| 301 | // constexpr' because there is a compile error in the branch not used. | 302 | // constexpr' because there is a compile error in the branch not used. |
| 302 | 303 | ||
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index b92c84316..d8c900e33 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <map> | ||
| 6 | #include <string_view> | 7 | #include <string_view> |
| 7 | #include <boost/container/flat_map.hpp> | 8 | #include "common/intrusive_list.h" |
| 8 | #include "core/file_sys/mode.h" | 9 | #include "core/file_sys/mode.h" |
| 9 | #include "core/file_sys/vfs.h" | 10 | #include "core/file_sys/vfs.h" |
| 10 | 11 | ||
| @@ -14,6 +15,11 @@ class IOFile; | |||
| 14 | 15 | ||
| 15 | namespace FileSys { | 16 | namespace FileSys { |
| 16 | 17 | ||
| 18 | struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { | ||
| 19 | std::shared_ptr<Common::FS::IOFile> file{}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | class RealVfsFile; | ||
| 17 | class RealVfsFilesystem : public VfsFilesystem { | 23 | class RealVfsFilesystem : public VfsFilesystem { |
| 18 | public: | 24 | public: |
| 19 | RealVfsFilesystem(); | 25 | RealVfsFilesystem(); |
| @@ -35,7 +41,21 @@ public: | |||
| 35 | bool DeleteDirectory(std::string_view path) override; | 41 | bool DeleteDirectory(std::string_view path) override; |
| 36 | 42 | ||
| 37 | private: | 43 | private: |
| 38 | boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; | 44 | using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType; |
| 45 | std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache; | ||
| 46 | ReferenceListType open_references; | ||
| 47 | ReferenceListType closed_references; | ||
| 48 | size_t num_open_files{}; | ||
| 49 | |||
| 50 | private: | ||
| 51 | friend class RealVfsFile; | ||
| 52 | void RefreshReference(const std::string& path, Mode perms, FileReference& reference); | ||
| 53 | void DropReference(std::unique_ptr<FileReference>&& reference); | ||
| 54 | void EvictSingleReference(); | ||
| 55 | |||
| 56 | private: | ||
| 57 | void InsertReferenceIntoList(FileReference& reference); | ||
| 58 | void RemoveReferenceFromList(FileReference& reference); | ||
| 39 | }; | 59 | }; |
| 40 | 60 | ||
| 41 | // An implementation of VfsFile that represents a file on the user's computer. | 61 | // An implementation of VfsFile that represents a file on the user's computer. |
| @@ -57,13 +77,11 @@ public: | |||
| 57 | bool Rename(std::string_view name) override; | 77 | bool Rename(std::string_view name) override; |
| 58 | 78 | ||
| 59 | private: | 79 | private: |
| 60 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, | 80 | RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, |
| 61 | const std::string& path, Mode perms = Mode::Read); | 81 | const std::string& path, Mode perms = Mode::Read); |
| 62 | 82 | ||
| 63 | void Close(); | ||
| 64 | |||
| 65 | RealVfsFilesystem& base; | 83 | RealVfsFilesystem& base; |
| 66 | std::shared_ptr<Common::FS::IOFile> backing; | 84 | std::unique_ptr<FileReference> reference; |
| 67 | std::string path; | 85 | std::string path; |
| 68 | std::string parent_path; | 86 | std::string parent_path; |
| 69 | std::vector<std::string> path_components; | 87 | std::vector<std::string> path_components; |
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 70480b725..908811e2c 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp | |||
| @@ -4,6 +4,8 @@ | |||
| 4 | #include <algorithm> | 4 | #include <algorithm> |
| 5 | #include <atomic> | 5 | #include <atomic> |
| 6 | #include <cinttypes> | 6 | #include <cinttypes> |
| 7 | #include <condition_variable> | ||
| 8 | #include <mutex> | ||
| 7 | #include <optional> | 9 | #include <optional> |
| 8 | #include <vector> | 10 | #include <vector> |
| 9 | 11 | ||
| @@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() { | |||
| 1313 | ASSERT(this->IsDummyThread()); | 1315 | ASSERT(this->IsDummyThread()); |
| 1314 | 1316 | ||
| 1315 | // We will block when the scheduler lock is released. | 1317 | // We will block when the scheduler lock is released. |
| 1316 | m_dummy_thread_runnable.store(false); | 1318 | std::scoped_lock lock{m_dummy_thread_mutex}; |
| 1319 | m_dummy_thread_runnable = false; | ||
| 1317 | } | 1320 | } |
| 1318 | 1321 | ||
| 1319 | void KThread::DummyThreadBeginWait() { | 1322 | void KThread::DummyThreadBeginWait() { |
| @@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() { | |||
| 1323 | } | 1326 | } |
| 1324 | 1327 | ||
| 1325 | // Block until runnable is no longer false. | 1328 | // Block until runnable is no longer false. |
| 1326 | m_dummy_thread_runnable.wait(false); | 1329 | std::unique_lock lock{m_dummy_thread_mutex}; |
| 1330 | m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); | ||
| 1327 | } | 1331 | } |
| 1328 | 1332 | ||
| 1329 | void KThread::DummyThreadEndWait() { | 1333 | void KThread::DummyThreadEndWait() { |
| @@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() { | |||
| 1331 | ASSERT(this->IsDummyThread()); | 1335 | ASSERT(this->IsDummyThread()); |
| 1332 | 1336 | ||
| 1333 | // Wake up the waiting thread. | 1337 | // Wake up the waiting thread. |
| 1334 | m_dummy_thread_runnable.store(true); | 1338 | { |
| 1335 | m_dummy_thread_runnable.notify_one(); | 1339 | std::scoped_lock lock{m_dummy_thread_mutex}; |
| 1340 | m_dummy_thread_runnable = true; | ||
| 1341 | } | ||
| 1342 | m_dummy_thread_cv.notify_one(); | ||
| 1336 | } | 1343 | } |
| 1337 | 1344 | ||
| 1338 | void KThread::BeginWait(KThreadQueue* queue) { | 1345 | void KThread::BeginWait(KThreadQueue* queue) { |
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index f9814ac8f..37fe5db77 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h | |||
| @@ -892,7 +892,9 @@ private: | |||
| 892 | std::shared_ptr<Common::Fiber> m_host_context{}; | 892 | std::shared_ptr<Common::Fiber> m_host_context{}; |
| 893 | ThreadType m_thread_type{}; | 893 | ThreadType m_thread_type{}; |
| 894 | StepState m_step_state{}; | 894 | StepState m_step_state{}; |
| 895 | std::atomic<bool> m_dummy_thread_runnable{true}; | 895 | bool m_dummy_thread_runnable{true}; |
| 896 | std::mutex m_dummy_thread_mutex{}; | ||
| 897 | std::condition_variable m_dummy_thread_cv{}; | ||
| 896 | 898 | ||
| 897 | // For debugging | 899 | // For debugging |
| 898 | std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; | 900 | std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; |
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp index b2bcb68c3..bc232c334 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp | |||
| @@ -36,12 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | |||
| 36 | 36 | ||
| 37 | // Validate UUID | 37 | // Validate UUID |
| 38 | constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` | 38 | constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` |
| 39 | if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != | 39 | if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) != |
| 40 | ntag_file.uuid.uid[3]) { | 40 | ntag_file.uuid.crc_check1) { |
| 41 | return false; | 41 | return false; |
| 42 | } | 42 | } |
| 43 | if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ | 43 | if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^ |
| 44 | ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { | 44 | ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) { |
| 45 | return false; | 45 | return false; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| @@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& ntag_file) { | |||
| 74 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | 74 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { |
| 75 | NTAG215File encoded_data{}; | 75 | NTAG215File encoded_data{}; |
| 76 | 76 | ||
| 77 | encoded_data.uid = nfc_data.uuid.uid; | 77 | encoded_data.uid = nfc_data.uuid; |
| 78 | encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; | 78 | encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2; |
| 79 | encoded_data.internal_number = nfc_data.internal_number; | ||
| 79 | encoded_data.static_lock = nfc_data.static_lock; | 80 | encoded_data.static_lock = nfc_data.static_lock; |
| 80 | encoded_data.compability_container = nfc_data.compability_container; | 81 | encoded_data.compability_container = nfc_data.compability_container; |
| 81 | encoded_data.hmac_data = nfc_data.user_memory.hmac_data; | 82 | encoded_data.hmac_data = nfc_data.user_memory.hmac_data; |
| @@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | |||
| 94 | encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; | 95 | encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; |
| 95 | encoded_data.application_area = nfc_data.user_memory.application_area; | 96 | encoded_data.application_area = nfc_data.user_memory.application_area; |
| 96 | encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; | 97 | encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; |
| 97 | encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; | ||
| 98 | encoded_data.model_info = nfc_data.user_memory.model_info; | 98 | encoded_data.model_info = nfc_data.user_memory.model_info; |
| 99 | encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; | 99 | encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; |
| 100 | encoded_data.dynamic_lock = nfc_data.dynamic_lock; | 100 | encoded_data.dynamic_lock = nfc_data.dynamic_lock; |
| @@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | |||
| 108 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | 108 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { |
| 109 | EncryptedNTAG215File nfc_data{}; | 109 | EncryptedNTAG215File nfc_data{}; |
| 110 | 110 | ||
| 111 | nfc_data.uuid.uid = encoded_data.uid; | 111 | nfc_data.uuid = encoded_data.uid; |
| 112 | nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; | 112 | nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2; |
| 113 | nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; | 113 | nfc_data.internal_number = encoded_data.internal_number; |
| 114 | nfc_data.static_lock = encoded_data.static_lock; | 114 | nfc_data.static_lock = encoded_data.static_lock; |
| 115 | nfc_data.compability_container = encoded_data.compability_container; | 115 | nfc_data.compability_container = encoded_data.compability_container; |
| 116 | nfc_data.user_memory.hmac_data = encoded_data.hmac_data; | 116 | nfc_data.user_memory.hmac_data = encoded_data.hmac_data; |
| @@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | |||
| 139 | return nfc_data; | 139 | return nfc_data; |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | u32 GetTagPassword(const TagUuid& uuid) { | ||
| 143 | // Verify that the generated password is correct | ||
| 144 | u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]); | ||
| 145 | password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8; | ||
| 146 | password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16; | ||
| 147 | password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24; | ||
| 148 | return password; | ||
| 149 | } | ||
| 150 | |||
| 151 | HashSeed GetSeed(const NTAG215File& data) { | 142 | HashSeed GetSeed(const NTAG215File& data) { |
| 152 | HashSeed seed{ | 143 | HashSeed seed{ |
| 153 | .magic = data.write_counter, | 144 | .magic = data.write_counter, |
| 154 | .padding = {}, | 145 | .padding = {}, |
| 155 | .uid_1 = data.uid, | 146 | .uid_1 = data.uid, |
| 156 | .nintendo_id_1 = data.nintendo_id, | ||
| 157 | .uid_2 = data.uid, | 147 | .uid_2 = data.uid, |
| 158 | .nintendo_id_2 = data.nintendo_id, | ||
| 159 | .keygen_salt = data.keygen_salt, | 148 | .keygen_salt = data.keygen_salt, |
| 160 | }; | 149 | }; |
| 161 | 150 | ||
| @@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed | |||
| 177 | output.insert(output.end(), key.magic_bytes.begin(), | 166 | output.insert(output.end(), key.magic_bytes.begin(), |
| 178 | key.magic_bytes.begin() + key.magic_length); | 167 | key.magic_bytes.begin() + key.magic_length); |
| 179 | 168 | ||
| 180 | output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); | 169 | std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{}; |
| 181 | output.emplace_back(seed.nintendo_id_1); | 170 | memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid)); |
| 182 | output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); | 171 | output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); |
| 183 | output.emplace_back(seed.nintendo_id_2); | 172 | memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid)); |
| 173 | output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); | ||
| 184 | 174 | ||
| 185 | for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | 175 | for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { |
| 186 | output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | 176 | output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); |
| @@ -264,8 +254,8 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou | |||
| 264 | 254 | ||
| 265 | // Copy the rest of the data directly | 255 | // Copy the rest of the data directly |
| 266 | out_data.uid = in_data.uid; | 256 | out_data.uid = in_data.uid; |
| 267 | out_data.nintendo_id = in_data.nintendo_id; | 257 | out_data.uid_crc_check2 = in_data.uid_crc_check2; |
| 268 | out_data.lock_bytes = in_data.lock_bytes; | 258 | out_data.internal_number = in_data.internal_number; |
| 269 | out_data.static_lock = in_data.static_lock; | 259 | out_data.static_lock = in_data.static_lock; |
| 270 | out_data.compability_container = in_data.compability_container; | 260 | out_data.compability_container = in_data.compability_container; |
| 271 | 261 | ||
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h index bf3044ed9..6a3e0841e 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.h +++ b/src/core/hle/service/nfc/common/amiibo_crypto.h | |||
| @@ -24,10 +24,8 @@ using DrgbOutput = std::array<u8, 0x20>; | |||
| 24 | struct HashSeed { | 24 | struct HashSeed { |
| 25 | u16_be magic; | 25 | u16_be magic; |
| 26 | std::array<u8, 0xE> padding; | 26 | std::array<u8, 0xE> padding; |
| 27 | NFC::UniqueSerialNumber uid_1; | 27 | TagUuid uid_1; |
| 28 | u8 nintendo_id_1; | 28 | TagUuid uid_2; |
| 29 | NFC::UniqueSerialNumber uid_2; | ||
| 30 | u8 nintendo_id_2; | ||
| 31 | std::array<u8, 0x20> keygen_salt; | 29 | std::array<u8, 0x20> keygen_salt; |
| 32 | }; | 30 | }; |
| 33 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | 31 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); |
| @@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | |||
| 69 | /// Converts from encoded file format to encrypted file format | 67 | /// Converts from encoded file format to encrypted file format |
| 70 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | 68 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); |
| 71 | 69 | ||
| 72 | /// Returns password needed to allow write access to protected memory | ||
| 73 | u32 GetTagPassword(const TagUuid& uuid); | ||
| 74 | |||
| 75 | // Generates Seed needed for key derivation | 70 | // Generates Seed needed for key derivation |
| 76 | HashSeed GetSeed(const NTAG215File& data); | 71 | HashSeed GetSeed(const NTAG215File& data); |
| 77 | 72 | ||
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index b14f682b5..f4b180b06 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -242,34 +242,39 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { | |||
| 242 | return ResultWrongDeviceState; | 242 | return ResultWrongDeviceState; |
| 243 | } | 243 | } |
| 244 | 244 | ||
| 245 | UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid; | 245 | UniqueSerialNumber uuid{}; |
| 246 | 246 | u8 uuid_length{}; | |
| 247 | // Generate random UUID to bypass amiibo load limits | 247 | NfcProtocol protocol{NfcProtocol::TypeA}; |
| 248 | if (Settings::values.random_amiibo_id) { | 248 | TagType tag_type{TagType::Type2}; |
| 249 | Common::TinyMT rng{}; | ||
| 250 | rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); | ||
| 251 | rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber)); | ||
| 252 | uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]; | ||
| 253 | } | ||
| 254 | 249 | ||
| 255 | if (is_mifare) { | 250 | if (is_mifare) { |
| 256 | tag_info = { | 251 | tag_type = TagType::Mifare; |
| 257 | .uuid = uuid, | 252 | uuid_length = sizeof(NFP::NtagTagUuid); |
| 258 | .uuid_extension = {}, | 253 | memcpy(uuid.data(), mifare_data.data(), uuid_length); |
| 259 | .uuid_length = static_cast<u8>(uuid.size()), | 254 | } else { |
| 260 | .protocol = NfcProtocol::TypeA, | 255 | tag_type = TagType::Type2; |
| 261 | .tag_type = TagType::Type4, | 256 | uuid_length = sizeof(NFP::NtagTagUuid); |
| 257 | NFP::NtagTagUuid nUuid{ | ||
| 258 | .part1 = encrypted_tag_data.uuid.part1, | ||
| 259 | .part2 = encrypted_tag_data.uuid.part2, | ||
| 260 | .nintendo_id = encrypted_tag_data.uuid.nintendo_id, | ||
| 262 | }; | 261 | }; |
| 263 | return ResultSuccess; | 262 | memcpy(uuid.data(), &nUuid, uuid_length); |
| 263 | |||
| 264 | // Generate random UUID to bypass amiibo load limits | ||
| 265 | if (Settings::values.random_amiibo_id) { | ||
| 266 | Common::TinyMT rng{}; | ||
| 267 | rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); | ||
| 268 | rng.GenerateRandomBytes(uuid.data(), uuid_length); | ||
| 269 | } | ||
| 264 | } | 270 | } |
| 265 | 271 | ||
| 266 | // Protocol and tag type may change here | 272 | // Protocol and tag type may change here |
| 267 | tag_info = { | 273 | tag_info = { |
| 268 | .uuid = uuid, | 274 | .uuid = uuid, |
| 269 | .uuid_extension = {}, | 275 | .uuid_length = uuid_length, |
| 270 | .uuid_length = static_cast<u8>(uuid.size()), | 276 | .protocol = protocol, |
| 271 | .protocol = NfcProtocol::TypeA, | 277 | .tag_type = tag_type, |
| 272 | .tag_type = TagType::Type2, | ||
| 273 | }; | 278 | }; |
| 274 | 279 | ||
| 275 | return ResultSuccess; | 280 | return ResultSuccess; |
| @@ -277,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { | |||
| 277 | 282 | ||
| 278 | Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, | 283 | Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, |
| 279 | std::span<MifareReadBlockData> read_block_data) const { | 284 | std::span<MifareReadBlockData> read_block_data) const { |
| 285 | if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { | ||
| 286 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||
| 287 | if (device_state == DeviceState::TagRemoved) { | ||
| 288 | return ResultTagRemoved; | ||
| 289 | } | ||
| 290 | return ResultWrongDeviceState; | ||
| 291 | } | ||
| 292 | |||
| 280 | Result result = ResultSuccess; | 293 | Result result = ResultSuccess; |
| 281 | 294 | ||
| 295 | TagInfo tag_info{}; | ||
| 296 | result = GetTagInfo(tag_info, true); | ||
| 297 | |||
| 298 | if (result.IsError()) { | ||
| 299 | return result; | ||
| 300 | } | ||
| 301 | |||
| 302 | if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { | ||
| 303 | return ResultInvalidTagType; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (parameters.size() == 0) { | ||
| 307 | return ResultInvalidArgument; | ||
| 308 | } | ||
| 309 | |||
| 310 | const auto unknown = parameters[0].sector_key.unknown; | ||
| 311 | for (std::size_t i = 0; i < parameters.size(); i++) { | ||
| 312 | if (unknown != parameters[i].sector_key.unknown) { | ||
| 313 | return ResultInvalidArgument; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 282 | for (std::size_t i = 0; i < parameters.size(); i++) { | 317 | for (std::size_t i = 0; i < parameters.size(); i++) { |
| 283 | result = ReadMifare(parameters[i], read_block_data[i]); | 318 | result = ReadMifare(parameters[i], read_block_data[i]); |
| 284 | if (result.IsError()) { | 319 | if (result.IsError()) { |
| @@ -293,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, | |||
| 293 | MifareReadBlockData& read_block_data) const { | 328 | MifareReadBlockData& read_block_data) const { |
| 294 | const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); | 329 | const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); |
| 295 | read_block_data.sector_number = parameter.sector_number; | 330 | read_block_data.sector_number = parameter.sector_number; |
| 296 | |||
| 297 | if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { | ||
| 298 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||
| 299 | if (device_state == DeviceState::TagRemoved) { | ||
| 300 | return ResultTagRemoved; | ||
| 301 | } | ||
| 302 | return ResultWrongDeviceState; | ||
| 303 | } | ||
| 304 | |||
| 305 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { | 331 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { |
| 306 | return Mifare::ResultReadError; | 332 | return ResultMifareError288; |
| 307 | } | 333 | } |
| 308 | 334 | ||
| 309 | // TODO: Use parameter.sector_key to read encrypted data | 335 | // TODO: Use parameter.sector_key to read encrypted data |
| @@ -315,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, | |||
| 315 | Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { | 341 | Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { |
| 316 | Result result = ResultSuccess; | 342 | Result result = ResultSuccess; |
| 317 | 343 | ||
| 344 | TagInfo tag_info{}; | ||
| 345 | result = GetTagInfo(tag_info, true); | ||
| 346 | |||
| 347 | if (result.IsError()) { | ||
| 348 | return result; | ||
| 349 | } | ||
| 350 | |||
| 351 | if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { | ||
| 352 | return ResultInvalidTagType; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (parameters.size() == 0) { | ||
| 356 | return ResultInvalidArgument; | ||
| 357 | } | ||
| 358 | |||
| 359 | const auto unknown = parameters[0].sector_key.unknown; | ||
| 360 | for (std::size_t i = 0; i < parameters.size(); i++) { | ||
| 361 | if (unknown != parameters[i].sector_key.unknown) { | ||
| 362 | return ResultInvalidArgument; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 318 | for (std::size_t i = 0; i < parameters.size(); i++) { | 366 | for (std::size_t i = 0; i < parameters.size(); i++) { |
| 319 | result = WriteMifare(parameters[i]); | 367 | result = WriteMifare(parameters[i]); |
| 320 | if (result.IsError()) { | 368 | if (result.IsError()) { |
| @@ -324,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet | |||
| 324 | 372 | ||
| 325 | if (!npad_device->WriteNfc(mifare_data)) { | 373 | if (!npad_device->WriteNfc(mifare_data)) { |
| 326 | LOG_ERROR(Service_NFP, "Error writing to file"); | 374 | LOG_ERROR(Service_NFP, "Error writing to file"); |
| 327 | return Mifare::ResultReadError; | 375 | return ResultMifareError288; |
| 328 | } | 376 | } |
| 329 | 377 | ||
| 330 | return result; | 378 | return result; |
| @@ -342,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { | |||
| 342 | } | 390 | } |
| 343 | 391 | ||
| 344 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { | 392 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { |
| 345 | return Mifare::ResultReadError; | 393 | return ResultMifareError288; |
| 346 | } | 394 | } |
| 347 | 395 | ||
| 348 | // TODO: Use parameter.sector_key to encrypt the data | 396 | // TODO: Use parameter.sector_key to encrypt the data |
| @@ -366,7 +414,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 366 | 414 | ||
| 367 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { | 415 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { |
| 368 | LOG_ERROR(Service_NFP, "Not an amiibo"); | 416 | LOG_ERROR(Service_NFP, "Not an amiibo"); |
| 369 | return ResultNotAnAmiibo; | 417 | return ResultInvalidTagType; |
| 370 | } | 418 | } |
| 371 | 419 | ||
| 372 | // The loaded amiibo is not encrypted | 420 | // The loaded amiibo is not encrypted |
| @@ -381,14 +429,14 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 381 | } | 429 | } |
| 382 | 430 | ||
| 383 | if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { | 431 | if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { |
| 384 | bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess(); | 432 | bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); |
| 385 | LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); | 433 | LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); |
| 386 | return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; | 434 | return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; |
| 387 | } | 435 | } |
| 388 | 436 | ||
| 389 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); | 437 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); |
| 390 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); | 438 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); |
| 391 | WriteBackupData(encrypted_tag_data.uuid.uid, data); | 439 | WriteBackupData(encrypted_tag_data.uuid, data); |
| 392 | 440 | ||
| 393 | device_state = DeviceState::TagMounted; | 441 | device_state = DeviceState::TagMounted; |
| 394 | mount_target = mount_target_; | 442 | mount_target = mount_target_; |
| @@ -492,7 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { | |||
| 492 | } | 540 | } |
| 493 | 541 | ||
| 494 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); | 542 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); |
| 495 | WriteBackupData(encrypted_tag_data.uuid.uid, data); | 543 | WriteBackupData(encrypted_tag_data.uuid, data); |
| 496 | } | 544 | } |
| 497 | 545 | ||
| 498 | if (!npad_device->WriteNfc(data)) { | 546 | if (!npad_device->WriteNfc(data)) { |
| @@ -520,7 +568,7 @@ Result NfcDevice::Restore() { | |||
| 520 | return result; | 568 | return result; |
| 521 | } | 569 | } |
| 522 | 570 | ||
| 523 | result = ReadBackupData(tag_info.uuid, data); | 571 | result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 524 | 572 | ||
| 525 | if (result.IsError()) { | 573 | if (result.IsError()) { |
| 526 | return result; | 574 | return result; |
| @@ -548,7 +596,7 @@ Result NfcDevice::Restore() { | |||
| 548 | } | 596 | } |
| 549 | 597 | ||
| 550 | if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { | 598 | if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { |
| 551 | return ResultNotAnAmiibo; | 599 | return ResultInvalidTagType; |
| 552 | } | 600 | } |
| 553 | 601 | ||
| 554 | if (!is_plain_amiibo) { | 602 | if (!is_plain_amiibo) { |
| @@ -1194,10 +1242,12 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) { | |||
| 1194 | return FlushWithBreak(break_type); | 1242 | return FlushWithBreak(break_type); |
| 1195 | } | 1243 | } |
| 1196 | 1244 | ||
| 1197 | Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { | 1245 | Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const { |
| 1246 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1198 | constexpr auto backup_dir = "backup"; | 1247 | constexpr auto backup_dir = "backup"; |
| 1199 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1248 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1200 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1249 | const auto file_name = |
| 1250 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1201 | 1251 | ||
| 1202 | if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { | 1252 | if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { |
| 1203 | return ResultUnableToAccessBackupFile; | 1253 | return ResultUnableToAccessBackupFile; |
| @@ -1206,10 +1256,19 @@ Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { | |||
| 1206 | return ResultSuccess; | 1256 | return ResultSuccess; |
| 1207 | } | 1257 | } |
| 1208 | 1258 | ||
| 1209 | Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const { | 1259 | Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const { |
| 1260 | UniqueSerialNumber uuid{}; | ||
| 1261 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1262 | return HasBackup(uuid, sizeof(NFP::TagUuid)); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 1266 | std::span<u8> data) const { | ||
| 1267 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1210 | constexpr auto backup_dir = "backup"; | 1268 | constexpr auto backup_dir = "backup"; |
| 1211 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1269 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1212 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1270 | const auto file_name = |
| 1271 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1213 | 1272 | ||
| 1214 | const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, | 1273 | const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, |
| 1215 | Common::FS::FileAccessMode::Read, | 1274 | Common::FS::FileAccessMode::Read, |
| @@ -1228,12 +1287,21 @@ Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u | |||
| 1228 | return ResultSuccess; | 1287 | return ResultSuccess; |
| 1229 | } | 1288 | } |
| 1230 | 1289 | ||
| 1231 | Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) { | 1290 | Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const { |
| 1291 | UniqueSerialNumber uuid{}; | ||
| 1292 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1293 | return ReadBackupData(uuid, sizeof(NFP::TagUuid), data); | ||
| 1294 | } | ||
| 1295 | |||
| 1296 | Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 1297 | std::span<const u8> data) { | ||
| 1298 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1232 | constexpr auto backup_dir = "backup"; | 1299 | constexpr auto backup_dir = "backup"; |
| 1233 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1300 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1234 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1301 | const auto file_name = |
| 1302 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1235 | 1303 | ||
| 1236 | if (HasBackup(uid).IsError()) { | 1304 | if (HasBackup(uid, uuid_size).IsError()) { |
| 1237 | if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { | 1305 | if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { |
| 1238 | return ResultBackupPathAlreadyExist; | 1306 | return ResultBackupPathAlreadyExist; |
| 1239 | } | 1307 | } |
| @@ -1260,6 +1328,12 @@ Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span< | |||
| 1260 | return ResultSuccess; | 1328 | return ResultSuccess; |
| 1261 | } | 1329 | } |
| 1262 | 1330 | ||
| 1331 | Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) { | ||
| 1332 | UniqueSerialNumber uuid{}; | ||
| 1333 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1334 | return WriteBackupData(uuid, sizeof(NFP::TagUuid), data); | ||
| 1335 | } | ||
| 1336 | |||
| 1263 | Result NfcDevice::WriteNtf(std::span<const u8> data) { | 1337 | Result NfcDevice::WriteNtf(std::span<const u8> data) { |
| 1264 | if (device_state != DeviceState::TagMounted) { | 1338 | if (device_state != DeviceState::TagMounted) { |
| 1265 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | 1339 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); |
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index 6f049b687..7560210d6 100644 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h | |||
| @@ -86,9 +86,14 @@ public: | |||
| 86 | Result GetAll(NFP::NfpData& data) const; | 86 | Result GetAll(NFP::NfpData& data) const; |
| 87 | Result SetAll(const NFP::NfpData& data); | 87 | Result SetAll(const NFP::NfpData& data); |
| 88 | Result BreakTag(NFP::BreakType break_type); | 88 | Result BreakTag(NFP::BreakType break_type); |
| 89 | Result HasBackup(const NFC::UniqueSerialNumber& uid) const; | 89 | Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const; |
| 90 | Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const; | 90 | Result HasBackup(const NFP::TagUuid& tag_uid) const; |
| 91 | Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data); | 91 | Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, |
| 92 | std::span<u8> data) const; | ||
| 93 | Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const; | ||
| 94 | Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 95 | std::span<const u8> data); | ||
| 96 | Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data); | ||
| 92 | Result WriteNtf(std::span<const u8> data); | 97 | Result WriteNtf(std::span<const u8> data); |
| 93 | 98 | ||
| 94 | u64 GetHandle() const; | 99 | u64 GetHandle() const; |
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp index cffd602df..b0456508e 100644 --- a/src/core/hle/service/nfc/common/device_manager.cpp +++ b/src/core/hle/service/nfc/common/device_manager.cpp | |||
| @@ -550,7 +550,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons | |||
| 550 | } | 550 | } |
| 551 | 551 | ||
| 552 | if (result.IsSuccess()) { | 552 | if (result.IsSuccess()) { |
| 553 | result = device->ReadBackupData(tag_info.uuid, data); | 553 | result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 554 | result = VerifyDeviceResult(device, result); | 554 | result = VerifyDeviceResult(device, result); |
| 555 | } | 555 | } |
| 556 | 556 | ||
| @@ -569,7 +569,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat | |||
| 569 | } | 569 | } |
| 570 | 570 | ||
| 571 | if (result.IsSuccess()) { | 571 | if (result.IsSuccess()) { |
| 572 | result = device->WriteBackupData(tag_info.uuid, data); | 572 | result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 573 | result = VerifyDeviceResult(device, result); | 573 | result = VerifyDeviceResult(device, result); |
| 574 | } | 574 | } |
| 575 | 575 | ||
diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h index 4b60048a5..16a9171e6 100644 --- a/src/core/hle/service/nfc/mifare_result.h +++ b/src/core/hle/service/nfc/mifare_result.h | |||
| @@ -12,6 +12,6 @@ constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65); | |||
| 12 | constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); | 12 | constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); |
| 13 | constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); | 13 | constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); |
| 14 | constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); | 14 | constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); |
| 15 | constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); | 15 | constexpr Result ResultNotAMifare(ErrorModule::NFCMifare, 288); |
| 16 | 16 | ||
| 17 | } // namespace Service::NFC::Mifare | 17 | } // namespace Service::NFC::Mifare |
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 198d0f2b9..130fb7f78 100644 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp | |||
| @@ -142,9 +142,13 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { | |||
| 142 | void NfcInterface::StartDetection(HLERequestContext& ctx) { | 142 | void NfcInterface::StartDetection(HLERequestContext& ctx) { |
| 143 | IPC::RequestParser rp{ctx}; | 143 | IPC::RequestParser rp{ctx}; |
| 144 | const auto device_handle{rp.Pop<u64>()}; | 144 | const auto device_handle{rp.Pop<u64>()}; |
| 145 | const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; | 145 | auto tag_protocol{NfcProtocol::All}; |
| 146 | LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); | 146 | |
| 147 | if (backend_type == BackendType::Nfc) { | ||
| 148 | tag_protocol = rp.PopEnum<NfcProtocol>(); | ||
| 149 | } | ||
| 147 | 150 | ||
| 151 | LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); | ||
| 148 | auto result = GetManager()->StartDetection(device_handle, tag_protocol); | 152 | auto result = GetManager()->StartDetection(device_handle, tag_protocol); |
| 149 | result = TranslateResultToServiceError(result); | 153 | result = TranslateResultToServiceError(result); |
| 150 | 154 | ||
| @@ -355,7 +359,7 @@ Result NfcInterface::TranslateResultToNfp(Result result) const { | |||
| 355 | if (result == ResultApplicationAreaExist) { | 359 | if (result == ResultApplicationAreaExist) { |
| 356 | return NFP::ResultApplicationAreaExist; | 360 | return NFP::ResultApplicationAreaExist; |
| 357 | } | 361 | } |
| 358 | if (result == ResultNotAnAmiibo) { | 362 | if (result == ResultInvalidTagType) { |
| 359 | return NFP::ResultNotAnAmiibo; | 363 | return NFP::ResultNotAnAmiibo; |
| 360 | } | 364 | } |
| 361 | if (result == ResultUnableToAccessBackupFile) { | 365 | if (result == ResultUnableToAccessBackupFile) { |
| @@ -381,6 +385,9 @@ Result NfcInterface::TranslateResultToMifare(Result result) const { | |||
| 381 | if (result == ResultTagRemoved) { | 385 | if (result == ResultTagRemoved) { |
| 382 | return Mifare::ResultTagRemoved; | 386 | return Mifare::ResultTagRemoved; |
| 383 | } | 387 | } |
| 388 | if (result == ResultInvalidTagType) { | ||
| 389 | return Mifare::ResultNotAMifare; | ||
| 390 | } | ||
| 384 | LOG_WARNING(Service_NFC, "Result conversion not handled"); | 391 | LOG_WARNING(Service_NFC, "Result conversion not handled"); |
| 385 | return result; | 392 | return result; |
| 386 | } | 393 | } |
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h index 59a808740..715c0e80c 100644 --- a/src/core/hle/service/nfc/nfc_result.h +++ b/src/core/hle/service/nfc/nfc_result.h | |||
| @@ -24,7 +24,8 @@ constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136); | |||
| 24 | constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); | 24 | constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); |
| 25 | constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); | 25 | constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); |
| 26 | constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); | 26 | constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); |
| 27 | constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178); | 27 | constexpr Result ResultInvalidTagType(ErrorModule::NFC, 178); |
| 28 | constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); | 28 | constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); |
| 29 | constexpr Result ResultMifareError288(ErrorModule::NFC, 288); | ||
| 29 | 30 | ||
| 30 | } // namespace Service::NFC | 31 | } // namespace Service::NFC |
diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h index c7ebd1fdb..68e724442 100644 --- a/src/core/hle/service/nfc/nfc_types.h +++ b/src/core/hle/service/nfc/nfc_types.h | |||
| @@ -35,32 +35,35 @@ enum class State : u32 { | |||
| 35 | 35 | ||
| 36 | // This is nn::nfc::TagType | 36 | // This is nn::nfc::TagType |
| 37 | enum class TagType : u32 { | 37 | enum class TagType : u32 { |
| 38 | None, | 38 | None = 0, |
| 39 | Type1, // ISO14443A RW 96-2k bytes 106kbit/s | 39 | Type1 = 1U << 0, // ISO14443A RW. Topaz |
| 40 | Type2, // ISO14443A RW/RO 540 bytes 106kbit/s | 40 | Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN |
| 41 | Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s | 41 | Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa |
| 42 | Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s | 42 | Type4A = 1U << 3, // ISO14443A RW/RO. DESFire |
| 43 | Type5, // ISO15693 RW/RO 540 bytes 106kbit/s | 43 | Type4B = 1U << 4, // ISO14443B RW/RO. DESFire |
| 44 | Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV | ||
| 45 | Mifare = 1U << 6, // Mifare classic. Skylanders | ||
| 46 | All = 0xFFFFFFFF, | ||
| 44 | }; | 47 | }; |
| 45 | 48 | ||
| 46 | enum class PackedTagType : u8 { | 49 | enum class PackedTagType : u8 { |
| 47 | None, | 50 | None = 0, |
| 48 | Type1, // ISO14443A RW 96-2k bytes 106kbit/s | 51 | Type1 = 1U << 0, // ISO14443A RW. Topaz |
| 49 | Type2, // ISO14443A RW/RO 540 bytes 106kbit/s | 52 | Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN |
| 50 | Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s | 53 | Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa |
| 51 | Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s | 54 | Type4A = 1U << 3, // ISO14443A RW/RO. DESFire |
| 52 | Type5, // ISO15693 RW/RO 540 bytes 106kbit/s | 55 | Type4B = 1U << 4, // ISO14443B RW/RO. DESFire |
| 56 | Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV | ||
| 57 | Mifare = 1U << 6, // Mifare classic. Skylanders | ||
| 58 | All = 0xFF, | ||
| 53 | }; | 59 | }; |
| 54 | 60 | ||
| 55 | // This is nn::nfc::NfcProtocol | 61 | // This is nn::nfc::NfcProtocol |
| 56 | // Verify this enum. It might be completely wrong default protocol is 0x48 | ||
| 57 | enum class NfcProtocol : u32 { | 62 | enum class NfcProtocol : u32 { |
| 58 | None, | 63 | None, |
| 59 | TypeA = 1U << 0, // ISO14443A | 64 | TypeA = 1U << 0, // ISO14443A |
| 60 | TypeB = 1U << 1, // ISO14443B | 65 | TypeB = 1U << 1, // ISO14443B |
| 61 | TypeF = 1U << 2, // Sony FeliCa | 66 | TypeF = 1U << 2, // Sony FeliCa |
| 62 | Unknown1 = 1U << 3, | ||
| 63 | Unknown2 = 1U << 5, | ||
| 64 | All = 0xFFFFFFFFU, | 67 | All = 0xFFFFFFFFU, |
| 65 | }; | 68 | }; |
| 66 | 69 | ||
| @@ -69,8 +72,7 @@ enum class TestWaveType : u32 { | |||
| 69 | Unknown, | 72 | Unknown, |
| 70 | }; | 73 | }; |
| 71 | 74 | ||
| 72 | using UniqueSerialNumber = std::array<u8, 7>; | 75 | using UniqueSerialNumber = std::array<u8, 10>; |
| 73 | using UniqueSerialNumberExtension = std::array<u8, 3>; | ||
| 74 | 76 | ||
| 75 | // This is nn::nfc::DeviceHandle | 77 | // This is nn::nfc::DeviceHandle |
| 76 | using DeviceHandle = u64; | 78 | using DeviceHandle = u64; |
| @@ -78,7 +80,6 @@ using DeviceHandle = u64; | |||
| 78 | // This is nn::nfc::TagInfo | 80 | // This is nn::nfc::TagInfo |
| 79 | struct TagInfo { | 81 | struct TagInfo { |
| 80 | UniqueSerialNumber uuid; | 82 | UniqueSerialNumber uuid; |
| 81 | UniqueSerialNumberExtension uuid_extension; | ||
| 82 | u8 uuid_length; | 83 | u8 uuid_length; |
| 83 | INSERT_PADDING_BYTES(0x15); | 84 | INSERT_PADDING_BYTES(0x15); |
| 84 | NfcProtocol protocol; | 85 | NfcProtocol protocol; |
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 7d36d5ee6..aed12a7f8 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h | |||
| @@ -85,7 +85,7 @@ enum class CabinetMode : u8 { | |||
| 85 | StartFormatter, | 85 | StartFormatter, |
| 86 | }; | 86 | }; |
| 87 | 87 | ||
| 88 | using LockBytes = std::array<u8, 2>; | 88 | using UuidPart = std::array<u8, 3>; |
| 89 | using HashData = std::array<u8, 0x20>; | 89 | using HashData = std::array<u8, 0x20>; |
| 90 | using ApplicationArea = std::array<u8, 0xD8>; | 90 | using ApplicationArea = std::array<u8, 0xD8>; |
| 91 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | 91 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; |
| @@ -93,12 +93,20 @@ using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | |||
| 93 | // This is nn::nfp::TagInfo | 93 | // This is nn::nfp::TagInfo |
| 94 | using TagInfo = NFC::TagInfo; | 94 | using TagInfo = NFC::TagInfo; |
| 95 | 95 | ||
| 96 | struct NtagTagUuid { | ||
| 97 | UuidPart part1; | ||
| 98 | UuidPart part2; | ||
| 99 | u8 nintendo_id; | ||
| 100 | }; | ||
| 101 | static_assert(sizeof(NtagTagUuid) == 7, "NtagTagUuid is an invalid size"); | ||
| 102 | |||
| 96 | struct TagUuid { | 103 | struct TagUuid { |
| 97 | NFC::UniqueSerialNumber uid; | 104 | UuidPart part1; |
| 105 | u8 crc_check1; | ||
| 106 | UuidPart part2; | ||
| 98 | u8 nintendo_id; | 107 | u8 nintendo_id; |
| 99 | LockBytes lock_bytes; | ||
| 100 | }; | 108 | }; |
| 101 | static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); | 109 | static_assert(sizeof(TagUuid) == 8, "TagUuid is an invalid size"); |
| 102 | 110 | ||
| 103 | struct WriteDate { | 111 | struct WriteDate { |
| 104 | u16 year; | 112 | u16 year; |
| @@ -231,7 +239,8 @@ struct EncryptedAmiiboFile { | |||
| 231 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | 239 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); |
| 232 | 240 | ||
| 233 | struct NTAG215File { | 241 | struct NTAG215File { |
| 234 | LockBytes lock_bytes; // Tag UUID | 242 | u8 uid_crc_check2; |
| 243 | u8 internal_number; | ||
| 235 | u16 static_lock; // Set defined pages as read only | 244 | u16 static_lock; // Set defined pages as read only |
| 236 | u32 compability_container; // Defines available memory | 245 | u32 compability_container; // Defines available memory |
| 237 | HashData hmac_data; // Hash | 246 | HashData hmac_data; // Hash |
| @@ -250,8 +259,7 @@ struct NTAG215File { | |||
| 250 | u32_be register_info_crc; | 259 | u32_be register_info_crc; |
| 251 | ApplicationArea application_area; // Encrypted Game data | 260 | ApplicationArea application_area; // Encrypted Game data |
| 252 | HashData hmac_tag; // Hash | 261 | HashData hmac_tag; // Hash |
| 253 | NFC::UniqueSerialNumber uid; // Unique serial number | 262 | TagUuid uid; |
| 254 | u8 nintendo_id; // Tag UUID | ||
| 255 | AmiiboModelInfo model_info; | 263 | AmiiboModelInfo model_info; |
| 256 | HashData keygen_salt; // Salt | 264 | HashData keygen_salt; // Salt |
| 257 | u32 dynamic_lock; // Dynamic lock | 265 | u32 dynamic_lock; // Dynamic lock |
| @@ -264,7 +272,9 @@ static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be tr | |||
| 264 | #pragma pack() | 272 | #pragma pack() |
| 265 | 273 | ||
| 266 | struct EncryptedNTAG215File { | 274 | struct EncryptedNTAG215File { |
| 267 | TagUuid uuid; // Unique serial number | 275 | TagUuid uuid; |
| 276 | u8 uuid_crc_check2; | ||
| 277 | u8 internal_number; | ||
| 268 | u16 static_lock; // Set defined pages as read only | 278 | u16 static_lock; // Set defined pages as read only |
| 269 | u32 compability_container; // Defines available memory | 279 | u32 compability_container; // Defines available memory |
| 270 | EncryptedAmiiboFile user_memory; // Writable data | 280 | EncryptedAmiiboFile user_memory; // Writable data |
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index e1728c06d..205371a26 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp | |||
| @@ -849,8 +849,9 @@ static Result CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& | |||
| 849 | static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, | 849 | static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, |
| 850 | CalendarTimeInternal& calendar_time, | 850 | CalendarTimeInternal& calendar_time, |
| 851 | CalendarAdditionalInfo& calendar_additional_info) { | 851 | CalendarAdditionalInfo& calendar_additional_info) { |
| 852 | if ((rules.go_ahead && time < rules.ats[0]) || | 852 | ASSERT(rules.go_ahead ? rules.time_count > 0 : true); |
| 853 | (rules.go_back && time > rules.ats[rules.time_count - 1])) { | 853 | if ((rules.go_back && time < rules.ats[0]) || |
| 854 | (rules.go_ahead && time > rules.ats[rules.time_count - 1])) { | ||
| 854 | s64 seconds{}; | 855 | s64 seconds{}; |
| 855 | if (time < rules.ats[0]) { | 856 | if (time < rules.ats[0]) { |
| 856 | seconds = rules.ats[0] - time; | 857 | seconds = rules.ats[0] - time; |
| @@ -910,9 +911,13 @@ static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, | |||
| 910 | 911 | ||
| 911 | calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; | 912 | calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; |
| 912 | const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; | 913 | const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; |
| 913 | for (int index{}; time_zone[index] != '\0'; ++index) { | 914 | u32 index; |
| 915 | for (index = 0; time_zone[index] != '\0' && time_zone[index] != ',' && | ||
| 916 | index < calendar_additional_info.timezone_name.size() - 1; | ||
| 917 | ++index) { | ||
| 914 | calendar_additional_info.timezone_name[index] = time_zone[index]; | 918 | calendar_additional_info.timezone_name[index] = time_zone[index]; |
| 915 | } | 919 | } |
| 920 | calendar_additional_info.timezone_name[index] = '\0'; | ||
| 916 | return ResultSuccess; | 921 | return ResultSuccess; |
| 917 | } | 922 | } |
| 918 | 923 | ||
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index e8273e152..8171c82a5 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp | |||
| @@ -112,20 +112,14 @@ void ITimeZoneService::LoadTimeZoneRule(HLERequestContext& ctx) { | |||
| 112 | LOG_DEBUG(Service_Time, "called, location_name={}", location_name); | 112 | LOG_DEBUG(Service_Time, "called, location_name={}", location_name); |
| 113 | 113 | ||
| 114 | TimeZone::TimeZoneRule time_zone_rule{}; | 114 | TimeZone::TimeZoneRule time_zone_rule{}; |
| 115 | if (const Result result{ | 115 | const Result result{time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; |
| 116 | time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; | ||
| 117 | result != ResultSuccess) { | ||
| 118 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 119 | rb.Push(result); | ||
| 120 | return; | ||
| 121 | } | ||
| 122 | 116 | ||
| 123 | std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); | 117 | std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); |
| 124 | std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); | 118 | std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); |
| 125 | ctx.WriteBuffer(time_zone_rule_outbuffer); | 119 | ctx.WriteBuffer(time_zone_rule_outbuffer); |
| 126 | 120 | ||
| 127 | IPC::ResponseBuilder rb{ctx, 2}; | 121 | IPC::ResponseBuilder rb{ctx, 2}; |
| 128 | rb.Push(ResultSuccess); | 122 | rb.Push(result); |
| 129 | } | 123 | } |
| 130 | 124 | ||
| 131 | void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) { | 125 | void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) { |
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index f8bafe553..6435b8af8 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp | |||
| @@ -82,6 +82,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { | |||
| 82 | switch (nfc_file.GetSize()) { | 82 | switch (nfc_file.GetSize()) { |
| 83 | case AmiiboSize: | 83 | case AmiiboSize: |
| 84 | case AmiiboSizeWithoutPassword: | 84 | case AmiiboSizeWithoutPassword: |
| 85 | case AmiiboSizeWithSignature: | ||
| 85 | data.resize(AmiiboSize); | 86 | data.resize(AmiiboSize); |
| 86 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { | 87 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { |
| 87 | return Info::NotAnAmiibo; | 88 | return Info::NotAnAmiibo; |
| @@ -109,6 +110,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) { | |||
| 109 | switch (data.size_bytes()) { | 110 | switch (data.size_bytes()) { |
| 110 | case AmiiboSize: | 111 | case AmiiboSize: |
| 111 | case AmiiboSizeWithoutPassword: | 112 | case AmiiboSizeWithoutPassword: |
| 113 | case AmiiboSizeWithSignature: | ||
| 112 | nfc_data.resize(AmiiboSize); | 114 | nfc_data.resize(AmiiboSize); |
| 113 | break; | 115 | break; |
| 114 | case MifareSize: | 116 | case MifareSize: |
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 34e97cd91..09ca09e68 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h | |||
| @@ -57,6 +57,7 @@ public: | |||
| 57 | private: | 57 | private: |
| 58 | static constexpr std::size_t AmiiboSize = 0x21C; | 58 | static constexpr std::size_t AmiiboSize = 0x21C; |
| 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; | 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; |
| 60 | static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20; | ||
| 60 | static constexpr std::size_t MifareSize = 0x400; | 61 | static constexpr std::size_t MifareSize = 0x400; |
| 61 | 62 | ||
| 62 | std::string file_path{}; | 63 | std::string file_path{}; |
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 251a4a880..45977d578 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -715,13 +715,19 @@ void BufferCache<P>::BindHostIndexBuffer() { | |||
| 715 | 715 | ||
| 716 | template <class P> | 716 | template <class P> |
| 717 | void BufferCache<P>::BindHostVertexBuffers() { | 717 | void BufferCache<P>::BindHostVertexBuffers() { |
| 718 | HostBindings host_bindings; | 718 | HostBindings<typename P::Buffer> host_bindings; |
| 719 | bool any_valid{false}; | 719 | bool any_valid{false}; |
| 720 | auto& flags = maxwell3d->dirty.flags; | 720 | auto& flags = maxwell3d->dirty.flags; |
| 721 | for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { | 721 | for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { |
| 722 | const Binding& binding = channel_state->vertex_buffers[index]; | ||
| 723 | Buffer& buffer = slot_buffers[binding.buffer_id]; | ||
| 724 | TouchBuffer(buffer, binding.buffer_id); | ||
| 725 | SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); | ||
| 722 | if (!flags[Dirty::VertexBuffer0 + index]) { | 726 | if (!flags[Dirty::VertexBuffer0 + index]) { |
| 723 | continue; | 727 | continue; |
| 724 | } | 728 | } |
| 729 | flags[Dirty::VertexBuffer0 + index] = false; | ||
| 730 | |||
| 725 | host_bindings.min_index = std::min(host_bindings.min_index, index); | 731 | host_bindings.min_index = std::min(host_bindings.min_index, index); |
| 726 | host_bindings.max_index = std::max(host_bindings.max_index, index); | 732 | host_bindings.max_index = std::max(host_bindings.max_index, index); |
| 727 | any_valid = true; | 733 | any_valid = true; |
| @@ -735,13 +741,10 @@ void BufferCache<P>::BindHostVertexBuffers() { | |||
| 735 | const Binding& binding = channel_state->vertex_buffers[index]; | 741 | const Binding& binding = channel_state->vertex_buffers[index]; |
| 736 | Buffer& buffer = slot_buffers[binding.buffer_id]; | 742 | Buffer& buffer = slot_buffers[binding.buffer_id]; |
| 737 | 743 | ||
| 738 | TouchBuffer(buffer, binding.buffer_id); | ||
| 739 | SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); | ||
| 740 | |||
| 741 | const u32 stride = maxwell3d->regs.vertex_streams[index].stride; | 744 | const u32 stride = maxwell3d->regs.vertex_streams[index].stride; |
| 742 | const u32 offset = buffer.Offset(binding.cpu_addr); | 745 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 743 | 746 | ||
| 744 | host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); | 747 | host_bindings.buffers.push_back(&buffer); |
| 745 | host_bindings.offsets.push_back(offset); | 748 | host_bindings.offsets.push_back(offset); |
| 746 | host_bindings.sizes.push_back(binding.size); | 749 | host_bindings.sizes.push_back(binding.size); |
| 747 | host_bindings.strides.push_back(stride); | 750 | host_bindings.strides.push_back(stride); |
| @@ -900,7 +903,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { | |||
| 900 | if (maxwell3d->regs.transform_feedback_enabled == 0) { | 903 | if (maxwell3d->regs.transform_feedback_enabled == 0) { |
| 901 | return; | 904 | return; |
| 902 | } | 905 | } |
| 903 | HostBindings host_bindings; | 906 | HostBindings<typename P::Buffer> host_bindings; |
| 904 | for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { | 907 | for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { |
| 905 | const Binding& binding = channel_state->transform_feedback_buffers[index]; | 908 | const Binding& binding = channel_state->transform_feedback_buffers[index]; |
| 906 | if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && | 909 | if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && |
| @@ -913,7 +916,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { | |||
| 913 | SynchronizeBuffer(buffer, binding.cpu_addr, size); | 916 | SynchronizeBuffer(buffer, binding.cpu_addr, size); |
| 914 | 917 | ||
| 915 | const u32 offset = buffer.Offset(binding.cpu_addr); | 918 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 916 | host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); | 919 | host_bindings.buffers.push_back(&buffer); |
| 917 | host_bindings.offsets.push_back(offset); | 920 | host_bindings.offsets.push_back(offset); |
| 918 | host_bindings.sizes.push_back(binding.size); | 921 | host_bindings.sizes.push_back(binding.size); |
| 919 | } | 922 | } |
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index cf359e241..63a120f7a 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h | |||
| @@ -105,8 +105,9 @@ static constexpr Binding NULL_BINDING{ | |||
| 105 | .buffer_id = NULL_BUFFER_ID, | 105 | .buffer_id = NULL_BUFFER_ID, |
| 106 | }; | 106 | }; |
| 107 | 107 | ||
| 108 | template <typename Buffer> | ||
| 108 | struct HostBindings { | 109 | struct HostBindings { |
| 109 | boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers; | 110 | boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers; |
| 110 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; | 111 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; |
| 111 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; | 112 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; |
| 112 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; | 113 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; |
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 0e94c521a..f34090791 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "common/settings.h" | ||
| 4 | #include "video_core/dirty_flags.h" | 5 | #include "video_core/dirty_flags.h" |
| 5 | #include "video_core/engines/draw_manager.h" | 6 | #include "video_core/engines/draw_manager.h" |
| 6 | #include "video_core/rasterizer_interface.h" | 7 | #include "video_core/rasterizer_interface.h" |
| @@ -195,8 +196,12 @@ void DrawManager::DrawTexture() { | |||
| 195 | if (lower_left) { | 196 | if (lower_left) { |
| 196 | draw_texture_state.dst_y0 -= dst_height; | 197 | draw_texture_state.dst_y0 -= dst_height; |
| 197 | } | 198 | } |
| 198 | draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width; | 199 | draw_texture_state.dst_x1 = |
| 199 | draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height; | 200 | draw_texture_state.dst_x0 + |
| 201 | static_cast<f32>(Settings::values.resolution_info.ScaleUp(static_cast<u32>(dst_width))); | ||
| 202 | draw_texture_state.dst_y1 = | ||
| 203 | draw_texture_state.dst_y0 + | ||
| 204 | static_cast<f32>(Settings::values.resolution_info.ScaleUp(static_cast<u32>(dst_height))); | ||
| 200 | draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f; | 205 | draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f; |
| 201 | draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f; | 206 | draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f; |
| 202 | draw_texture_state.src_x1 = | 207 | draw_texture_state.src_x1 = |
| @@ -207,7 +212,6 @@ void DrawManager::DrawTexture() { | |||
| 207 | draw_texture_state.src_y0; | 212 | draw_texture_state.src_y0; |
| 208 | draw_texture_state.src_sampler = regs.draw_texture.src_sampler; | 213 | draw_texture_state.src_sampler = regs.draw_texture.src_sampler; |
| 209 | draw_texture_state.src_texture = regs.draw_texture.src_texture; | 214 | draw_texture_state.src_texture = regs.draw_texture.src_texture; |
| 210 | |||
| 211 | maxwell3d->rasterizer->DrawTexture(); | 215 | maxwell3d->rasterizer->DrawTexture(); |
| 212 | } | 216 | } |
| 213 | 217 | ||
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 7b2cde7a7..b2f7e160a 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp | |||
| @@ -111,7 +111,7 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cp | |||
| 111 | [[maybe_unused]] const auto current_entry_type = GetEntry<false>(current_gpu_addr); | 111 | [[maybe_unused]] const auto current_entry_type = GetEntry<false>(current_gpu_addr); |
| 112 | SetEntry<false>(current_gpu_addr, entry_type); | 112 | SetEntry<false>(current_gpu_addr, entry_type); |
| 113 | if (current_entry_type != entry_type) { | 113 | if (current_entry_type != entry_type) { |
| 114 | rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, page_size); | 114 | rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, page_size); |
| 115 | } | 115 | } |
| 116 | if constexpr (entry_type == EntryType::Mapped) { | 116 | if constexpr (entry_type == EntryType::Mapped) { |
| 117 | const VAddr current_cpu_addr = cpu_addr + offset; | 117 | const VAddr current_cpu_addr = cpu_addr + offset; |
| @@ -134,7 +134,7 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr | |||
| 134 | [[maybe_unused]] const auto current_entry_type = GetEntry<true>(current_gpu_addr); | 134 | [[maybe_unused]] const auto current_entry_type = GetEntry<true>(current_gpu_addr); |
| 135 | SetEntry<true>(current_gpu_addr, entry_type); | 135 | SetEntry<true>(current_gpu_addr, entry_type); |
| 136 | if (current_entry_type != entry_type) { | 136 | if (current_entry_type != entry_type) { |
| 137 | rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, big_page_size); | 137 | rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, big_page_size); |
| 138 | } | 138 | } |
| 139 | if constexpr (entry_type == EntryType::Mapped) { | 139 | if constexpr (entry_type == EntryType::Mapped) { |
| 140 | const VAddr current_cpu_addr = cpu_addr + offset; | 140 | const VAddr current_cpu_addr = cpu_addr + offset; |
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 0cc546a3a..38d553d3c 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp | |||
| @@ -232,12 +232,12 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, | |||
| 232 | } | 232 | } |
| 233 | } | 233 | } |
| 234 | 234 | ||
| 235 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { | 235 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 236 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 236 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 237 | BindVertexBuffer( | 237 | BindVertexBuffer(bindings.min_index + index, *bindings.buffers[index], |
| 238 | bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]), | 238 | static_cast<u32>(bindings.offsets[index]), |
| 239 | static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]), | 239 | static_cast<u32>(bindings.sizes[index]), |
| 240 | static_cast<u32>(bindings.strides[index])); | 240 | static_cast<u32>(bindings.strides[index])); |
| 241 | } | 241 | } |
| 242 | } | 242 | } |
| 243 | 243 | ||
| @@ -329,10 +329,9 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, | |||
| 329 | static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); | 329 | static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); |
| 330 | } | 330 | } |
| 331 | 331 | ||
| 332 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { | 332 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 333 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 333 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 334 | glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, | 334 | glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, bindings.buffers[index]->Handle(), |
| 335 | reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(), | ||
| 336 | static_cast<GLintptr>(bindings.offsets[index]), | 335 | static_cast<GLintptr>(bindings.offsets[index]), |
| 337 | static_cast<GLsizeiptr>(bindings.sizes[index])); | 336 | static_cast<GLsizeiptr>(bindings.sizes[index])); |
| 338 | } | 337 | } |
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index e4e000284..41b746f3b 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h | |||
| @@ -87,7 +87,8 @@ public: | |||
| 87 | void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); | 87 | void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); |
| 88 | 88 | ||
| 89 | void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); | 89 | void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); |
| 90 | void BindVertexBuffers(VideoCommon::HostBindings& bindings); | 90 | |
| 91 | void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 91 | 92 | ||
| 92 | void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); | 93 | void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); |
| 93 | 94 | ||
| @@ -100,7 +101,8 @@ public: | |||
| 100 | bool is_written); | 101 | bool is_written); |
| 101 | 102 | ||
| 102 | void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); | 103 | void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); |
| 103 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); | 104 | |
| 105 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 104 | 106 | ||
| 105 | void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, | 107 | void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, |
| 106 | VideoCore::Surface::PixelFormat format); | 108 | VideoCore::Surface::PixelFormat format); |
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 1a0cea9b7..3151c0db8 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp | |||
| @@ -87,7 +87,8 @@ void ComputePipeline::Configure() { | |||
| 87 | texture_cache.SynchronizeComputeDescriptors(); | 87 | texture_cache.SynchronizeComputeDescriptors(); |
| 88 | 88 | ||
| 89 | boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; | 89 | boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; |
| 90 | std::array<GLuint, MAX_TEXTURES> samplers; | 90 | boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers; |
| 91 | std::array<GLuint, MAX_TEXTURES> gl_samplers; | ||
| 91 | std::array<GLuint, MAX_TEXTURES> textures; | 92 | std::array<GLuint, MAX_TEXTURES> textures; |
| 92 | std::array<GLuint, MAX_IMAGES> images; | 93 | std::array<GLuint, MAX_IMAGES> images; |
| 93 | GLsizei sampler_binding{}; | 94 | GLsizei sampler_binding{}; |
| @@ -131,7 +132,6 @@ void ComputePipeline::Configure() { | |||
| 131 | for (u32 index = 0; index < desc.count; ++index) { | 132 | for (u32 index = 0; index < desc.count; ++index) { |
| 132 | const auto handle{read_handle(desc, index)}; | 133 | const auto handle{read_handle(desc, index)}; |
| 133 | views.push_back({handle.first}); | 134 | views.push_back({handle.first}); |
| 134 | samplers[sampler_binding++] = 0; | ||
| 135 | } | 135 | } |
| 136 | } | 136 | } |
| 137 | for (const auto& desc : info.image_buffer_descriptors) { | 137 | for (const auto& desc : info.image_buffer_descriptors) { |
| @@ -142,8 +142,8 @@ void ComputePipeline::Configure() { | |||
| 142 | const auto handle{read_handle(desc, index)}; | 142 | const auto handle{read_handle(desc, index)}; |
| 143 | views.push_back({handle.first}); | 143 | views.push_back({handle.first}); |
| 144 | 144 | ||
| 145 | Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); | 145 | VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); |
| 146 | samplers[sampler_binding++] = sampler->Handle(); | 146 | samplers.push_back(sampler); |
| 147 | } | 147 | } |
| 148 | } | 148 | } |
| 149 | for (const auto& desc : info.image_descriptors) { | 149 | for (const auto& desc : info.image_descriptors) { |
| @@ -186,10 +186,17 @@ void ComputePipeline::Configure() { | |||
| 186 | 186 | ||
| 187 | const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + | 187 | const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + |
| 188 | num_image_buffers}; | 188 | num_image_buffers}; |
| 189 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; | ||
| 189 | texture_binding += num_texture_buffers; | 190 | texture_binding += num_texture_buffers; |
| 190 | image_binding += num_image_buffers; | 191 | image_binding += num_image_buffers; |
| 191 | 192 | ||
| 192 | u32 texture_scaling_mask{}; | 193 | u32 texture_scaling_mask{}; |
| 194 | |||
| 195 | for (const auto& desc : info.texture_buffer_descriptors) { | ||
| 196 | for (u32 index = 0; index < desc.count; ++index) { | ||
| 197 | gl_samplers[sampler_binding++] = 0; | ||
| 198 | } | ||
| 199 | } | ||
| 193 | for (const auto& desc : info.texture_descriptors) { | 200 | for (const auto& desc : info.texture_descriptors) { |
| 194 | for (u32 index = 0; index < desc.count; ++index) { | 201 | for (u32 index = 0; index < desc.count; ++index) { |
| 195 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; | 202 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; |
| @@ -198,6 +205,12 @@ void ComputePipeline::Configure() { | |||
| 198 | texture_scaling_mask |= 1u << texture_binding; | 205 | texture_scaling_mask |= 1u << texture_binding; |
| 199 | } | 206 | } |
| 200 | ++texture_binding; | 207 | ++texture_binding; |
| 208 | |||
| 209 | const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; | ||
| 210 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 211 | !image_view.SupportsAnisotropy()}; | ||
| 212 | gl_samplers[sampler_binding++] = | ||
| 213 | use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); | ||
| 201 | } | 214 | } |
| 202 | } | 215 | } |
| 203 | u32 image_scaling_mask{}; | 216 | u32 image_scaling_mask{}; |
| @@ -228,7 +241,7 @@ void ComputePipeline::Configure() { | |||
| 228 | if (texture_binding != 0) { | 241 | if (texture_binding != 0) { |
| 229 | ASSERT(texture_binding == sampler_binding); | 242 | ASSERT(texture_binding == sampler_binding); |
| 230 | glBindTextures(0, texture_binding, textures.data()); | 243 | glBindTextures(0, texture_binding, textures.data()); |
| 231 | glBindSamplers(0, sampler_binding, samplers.data()); | 244 | glBindSamplers(0, sampler_binding, gl_samplers.data()); |
| 232 | } | 245 | } |
| 233 | if (image_binding != 0) { | 246 | if (image_binding != 0) { |
| 234 | glBindImageTextures(0, image_binding, images.data()); | 247 | glBindImageTextures(0, image_binding, images.data()); |
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index 89000d6e0..c58f760b8 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp | |||
| @@ -275,9 +275,9 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c | |||
| 275 | template <typename Spec> | 275 | template <typename Spec> |
| 276 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | 276 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { |
| 277 | std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; | 277 | std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; |
| 278 | std::array<GLuint, MAX_TEXTURES> samplers; | 278 | std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers; |
| 279 | size_t views_index{}; | 279 | size_t views_index{}; |
| 280 | GLsizei sampler_binding{}; | 280 | size_t samplers_index{}; |
| 281 | 281 | ||
| 282 | texture_cache.SynchronizeGraphicsDescriptors(); | 282 | texture_cache.SynchronizeGraphicsDescriptors(); |
| 283 | 283 | ||
| @@ -337,7 +337,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 337 | for (u32 index = 0; index < desc.count; ++index) { | 337 | for (u32 index = 0; index < desc.count; ++index) { |
| 338 | const auto handle{read_handle(desc, index)}; | 338 | const auto handle{read_handle(desc, index)}; |
| 339 | views[views_index++] = {handle.first}; | 339 | views[views_index++] = {handle.first}; |
| 340 | samplers[sampler_binding++] = 0; | ||
| 341 | } | 340 | } |
| 342 | } | 341 | } |
| 343 | } | 342 | } |
| @@ -351,8 +350,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 351 | const auto handle{read_handle(desc, index)}; | 350 | const auto handle{read_handle(desc, index)}; |
| 352 | views[views_index++] = {handle.first}; | 351 | views[views_index++] = {handle.first}; |
| 353 | 352 | ||
| 354 | Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; | 353 | VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; |
| 355 | samplers[sampler_binding++] = sampler->Handle(); | 354 | samplers[samplers_index++] = sampler; |
| 356 | } | 355 | } |
| 357 | } | 356 | } |
| 358 | if constexpr (Spec::has_images) { | 357 | if constexpr (Spec::has_images) { |
| @@ -445,10 +444,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 445 | program_manager.BindSourcePrograms(source_programs); | 444 | program_manager.BindSourcePrograms(source_programs); |
| 446 | } | 445 | } |
| 447 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 446 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 447 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; | ||
| 448 | GLsizei texture_binding = 0; | 448 | GLsizei texture_binding = 0; |
| 449 | GLsizei image_binding = 0; | 449 | GLsizei image_binding = 0; |
| 450 | GLsizei sampler_binding{}; | ||
| 450 | std::array<GLuint, MAX_TEXTURES> textures; | 451 | std::array<GLuint, MAX_TEXTURES> textures; |
| 451 | std::array<GLuint, MAX_IMAGES> images; | 452 | std::array<GLuint, MAX_IMAGES> images; |
| 453 | std::array<GLuint, MAX_TEXTURES> gl_samplers; | ||
| 452 | const auto prepare_stage{[&](size_t stage) { | 454 | const auto prepare_stage{[&](size_t stage) { |
| 453 | buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); | 455 | buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); |
| 454 | buffer_cache.BindHostStageBuffers(stage); | 456 | buffer_cache.BindHostStageBuffers(stage); |
| @@ -465,6 +467,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 465 | u32 stage_image_binding{}; | 467 | u32 stage_image_binding{}; |
| 466 | 468 | ||
| 467 | const auto& info{stage_infos[stage]}; | 469 | const auto& info{stage_infos[stage]}; |
| 470 | if constexpr (Spec::has_texture_buffers) { | ||
| 471 | for (const auto& desc : info.texture_buffer_descriptors) { | ||
| 472 | for (u32 index = 0; index < desc.count; ++index) { | ||
| 473 | gl_samplers[sampler_binding++] = 0; | ||
| 474 | } | ||
| 475 | } | ||
| 476 | } | ||
| 468 | for (const auto& desc : info.texture_descriptors) { | 477 | for (const auto& desc : info.texture_descriptors) { |
| 469 | for (u32 index = 0; index < desc.count; ++index) { | 478 | for (u32 index = 0; index < desc.count; ++index) { |
| 470 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; | 479 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; |
| @@ -474,6 +483,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 474 | } | 483 | } |
| 475 | ++texture_binding; | 484 | ++texture_binding; |
| 476 | ++stage_texture_binding; | 485 | ++stage_texture_binding; |
| 486 | |||
| 487 | const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; | ||
| 488 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 489 | !image_view.SupportsAnisotropy()}; | ||
| 490 | gl_samplers[sampler_binding++] = | ||
| 491 | use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); | ||
| 477 | } | 492 | } |
| 478 | } | 493 | } |
| 479 | for (const auto& desc : info.image_descriptors) { | 494 | for (const auto& desc : info.image_descriptors) { |
| @@ -534,7 +549,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 534 | if (texture_binding != 0) { | 549 | if (texture_binding != 0) { |
| 535 | ASSERT(texture_binding == sampler_binding); | 550 | ASSERT(texture_binding == sampler_binding); |
| 536 | glBindTextures(0, texture_binding, textures.data()); | 551 | glBindTextures(0, texture_binding, textures.data()); |
| 537 | glBindSamplers(0, sampler_binding, samplers.data()); | 552 | glBindSamplers(0, sampler_binding, gl_samplers.data()); |
| 538 | } | 553 | } |
| 539 | if (image_binding != 0) { | 554 | if (image_binding != 0) { |
| 540 | glBindImageTextures(0, image_binding, images.data()); | 555 | glBindImageTextures(0, image_binding, images.data()); |
diff --git a/src/video_core/renderer_opengl/gl_shader_context.h b/src/video_core/renderer_opengl/gl_shader_context.h index 207a75d42..d12cd06fa 100644 --- a/src/video_core/renderer_opengl/gl_shader_context.h +++ b/src/video_core/renderer_opengl/gl_shader_context.h | |||
| @@ -16,9 +16,9 @@ struct ShaderPools { | |||
| 16 | inst.ReleaseContents(); | 16 | inst.ReleaseContents(); |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | Shader::ObjectPool<Shader::IR::Inst> inst; | 19 | Shader::ObjectPool<Shader::IR::Inst> inst{8192}; |
| 20 | Shader::ObjectPool<Shader::IR::Block> block; | 20 | Shader::ObjectPool<Shader::IR::Block> block{32}; |
| 21 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; | 21 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; |
| 22 | }; | 22 | }; |
| 23 | 23 | ||
| 24 | struct Context { | 24 | struct Context { |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 1c5dbcdd8..3b446be07 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -1268,36 +1268,48 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) { | |||
| 1268 | 1268 | ||
| 1269 | UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); | 1269 | UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); |
| 1270 | 1270 | ||
| 1271 | sampler.Create(); | 1271 | const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); |
| 1272 | const GLuint handle = sampler.handle; | 1272 | |
| 1273 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); | 1273 | const auto create_sampler = [&](const f32 anisotropy) { |
| 1274 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); | 1274 | OGLSampler new_sampler; |
| 1275 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); | 1275 | new_sampler.Create(); |
| 1276 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); | 1276 | const GLuint handle = new_sampler.handle; |
| 1277 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); | 1277 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); |
| 1278 | glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); | 1278 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); |
| 1279 | glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); | 1279 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); |
| 1280 | glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); | 1280 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); |
| 1281 | glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); | 1281 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); |
| 1282 | glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); | 1282 | glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); |
| 1283 | glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); | 1283 | glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); |
| 1284 | 1284 | glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); | |
| 1285 | if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { | 1285 | glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); |
| 1286 | const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); | 1286 | glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); |
| 1287 | glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropy); | 1287 | glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); |
| 1288 | } else { | 1288 | |
| 1289 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); | 1289 | if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { |
| 1290 | } | 1290 | glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, anisotropy); |
| 1291 | if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { | 1291 | } else { |
| 1292 | glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); | 1292 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); |
| 1293 | } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { | 1293 | } |
| 1294 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); | 1294 | if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { |
| 1295 | } | 1295 | glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); |
| 1296 | if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { | 1296 | } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { |
| 1297 | glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); | 1297 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); |
| 1298 | } else if (seamless == GL_FALSE) { | 1298 | } |
| 1299 | // We default to false because it's more common | 1299 | if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { |
| 1300 | LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); | 1300 | glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); |
| 1301 | } else if (seamless == GL_FALSE) { | ||
| 1302 | // We default to false because it's more common | ||
| 1303 | LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); | ||
| 1304 | } | ||
| 1305 | return new_sampler; | ||
| 1306 | }; | ||
| 1307 | |||
| 1308 | sampler = create_sampler(max_anisotropy); | ||
| 1309 | |||
| 1310 | const f32 max_anisotropy_default = static_cast<f32>(1U << config.max_anisotropy); | ||
| 1311 | if (max_anisotropy > max_anisotropy_default) { | ||
| 1312 | sampler_default_anisotropy = create_sampler(max_anisotropy_default); | ||
| 1301 | } | 1313 | } |
| 1302 | } | 1314 | } |
| 1303 | 1315 | ||
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 1148b73d7..3676eaaa9 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -309,12 +309,21 @@ class Sampler { | |||
| 309 | public: | 309 | public: |
| 310 | explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); | 310 | explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); |
| 311 | 311 | ||
| 312 | GLuint Handle() const noexcept { | 312 | [[nodiscard]] GLuint Handle() const noexcept { |
| 313 | return sampler.handle; | 313 | return sampler.handle; |
| 314 | } | 314 | } |
| 315 | 315 | ||
| 316 | [[nodiscard]] GLuint HandleWithDefaultAnisotropy() const noexcept { | ||
| 317 | return sampler_default_anisotropy.handle; | ||
| 318 | } | ||
| 319 | |||
| 320 | [[nodiscard]] bool HasAddedAnisotropy() const noexcept { | ||
| 321 | return static_cast<bool>(sampler_default_anisotropy.handle); | ||
| 322 | } | ||
| 323 | |||
| 316 | private: | 324 | private: |
| 317 | OGLSampler sampler; | 325 | OGLSampler sampler; |
| 326 | OGLSampler sampler_default_anisotropy; | ||
| 318 | }; | 327 | }; |
| 319 | 328 | ||
| 320 | class Framebuffer { | 329 | class Framebuffer { |
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 983e1c2e1..71c783709 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h | |||
| @@ -178,7 +178,7 @@ public: | |||
| 178 | inline void PushImageDescriptors(TextureCache& texture_cache, | 178 | inline void PushImageDescriptors(TextureCache& texture_cache, |
| 179 | GuestDescriptorQueue& guest_descriptor_queue, | 179 | GuestDescriptorQueue& guest_descriptor_queue, |
| 180 | const Shader::Info& info, RescalingPushConstant& rescaling, | 180 | const Shader::Info& info, RescalingPushConstant& rescaling, |
| 181 | const VkSampler*& samplers, | 181 | const VideoCommon::SamplerId*& samplers, |
| 182 | const VideoCommon::ImageViewInOut*& views) { | 182 | const VideoCommon::ImageViewInOut*& views) { |
| 183 | const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); | 183 | const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); |
| 184 | const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); | 184 | const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); |
| @@ -187,10 +187,15 @@ inline void PushImageDescriptors(TextureCache& texture_cache, | |||
| 187 | for (const auto& desc : info.texture_descriptors) { | 187 | for (const auto& desc : info.texture_descriptors) { |
| 188 | for (u32 index = 0; index < desc.count; ++index) { | 188 | for (u32 index = 0; index < desc.count; ++index) { |
| 189 | const VideoCommon::ImageViewId image_view_id{(views++)->id}; | 189 | const VideoCommon::ImageViewId image_view_id{(views++)->id}; |
| 190 | const VkSampler sampler{*(samplers++)}; | 190 | const VideoCommon::SamplerId sampler_id{*(samplers++)}; |
| 191 | ImageView& image_view{texture_cache.GetImageView(image_view_id)}; | 191 | ImageView& image_view{texture_cache.GetImageView(image_view_id)}; |
| 192 | const VkImageView vk_image_view{image_view.Handle(desc.type)}; | 192 | const VkImageView vk_image_view{image_view.Handle(desc.type)}; |
| 193 | guest_descriptor_queue.AddSampledImage(vk_image_view, sampler); | 193 | const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; |
| 194 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 195 | !image_view.SupportsAnisotropy()}; | ||
| 196 | const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() | ||
| 197 | : sampler.Handle()}; | ||
| 198 | guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); | ||
| 194 | rescaling.PushTexture(texture_cache.IsRescaling(image_view)); | 199 | rescaling.PushTexture(texture_cache.IsRescaling(image_view)); |
| 195 | } | 200 | } |
| 196 | } | 201 | } |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index d72d99899..e30fcb1ed 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp | |||
| @@ -501,11 +501,10 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset | |||
| 501 | } | 501 | } |
| 502 | } | 502 | } |
| 503 | 503 | ||
| 504 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { | 504 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 505 | boost::container::small_vector<VkBuffer, 32> buffer_handles; | 505 | boost::container::small_vector<VkBuffer, 32> buffer_handles; |
| 506 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 506 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 507 | auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); | 507 | auto handle = bindings.buffers[index]->Handle(); |
| 508 | auto handle = buffer.Handle(); | ||
| 509 | if (handle == VK_NULL_HANDLE) { | 508 | if (handle == VK_NULL_HANDLE) { |
| 510 | bindings.offsets[index] = 0; | 509 | bindings.offsets[index] = 0; |
| 511 | bindings.sizes[index] = VK_WHOLE_SIZE; | 510 | bindings.sizes[index] = VK_WHOLE_SIZE; |
| @@ -517,20 +516,17 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) | |||
| 517 | buffer_handles.push_back(handle); | 516 | buffer_handles.push_back(handle); |
| 518 | } | 517 | } |
| 519 | if (device.IsExtExtendedDynamicStateSupported()) { | 518 | if (device.IsExtExtendedDynamicStateSupported()) { |
| 520 | scheduler.Record([bindings = bindings, | 519 | scheduler.Record([bindings = std::move(bindings), |
| 521 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 520 | buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { |
| 522 | cmdbuf.BindVertexBuffers2EXT( | 521 | cmdbuf.BindVertexBuffers2EXT( |
| 523 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), | 522 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), |
| 524 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), | 523 | bindings.offsets.data(), bindings.sizes.data(), bindings.strides.data()); |
| 525 | reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()), | ||
| 526 | reinterpret_cast<const VkDeviceSize*>(bindings.strides.data())); | ||
| 527 | }); | 524 | }); |
| 528 | } else { | 525 | } else { |
| 529 | scheduler.Record([bindings = bindings, | 526 | scheduler.Record([bindings = std::move(bindings), |
| 530 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 527 | buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { |
| 531 | cmdbuf.BindVertexBuffers( | 528 | cmdbuf.BindVertexBuffers(bindings.min_index, bindings.max_index - bindings.min_index, |
| 532 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), | 529 | buffer_handles.data(), bindings.offsets.data()); |
| 533 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data())); | ||
| 534 | }); | 530 | }); |
| 535 | } | 531 | } |
| 536 | } | 532 | } |
| @@ -556,23 +552,21 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, | |||
| 556 | }); | 552 | }); |
| 557 | } | 553 | } |
| 558 | 554 | ||
| 559 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { | 555 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 560 | if (!device.IsExtTransformFeedbackSupported()) { | 556 | if (!device.IsExtTransformFeedbackSupported()) { |
| 561 | // Already logged in the rasterizer | 557 | // Already logged in the rasterizer |
| 562 | return; | 558 | return; |
| 563 | } | 559 | } |
| 564 | boost::container::small_vector<VkBuffer, 4> buffer_handles; | 560 | boost::container::small_vector<VkBuffer, 4> buffer_handles; |
| 565 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 561 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 566 | auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); | 562 | buffer_handles.push_back(bindings.buffers[index]->Handle()); |
| 567 | buffer_handles.push_back(buffer.Handle()); | 563 | } |
| 568 | } | 564 | scheduler.Record([bindings = std::move(bindings), |
| 569 | scheduler.Record( | 565 | buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { |
| 570 | [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 566 | cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles.size()), |
| 571 | cmdbuf.BindTransformFeedbackBuffersEXT( | 567 | buffer_handles.data(), bindings.offsets.data(), |
| 572 | 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(), | 568 | bindings.sizes.data()); |
| 573 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), | 569 | }); |
| 574 | reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data())); | ||
| 575 | }); | ||
| 576 | } | 570 | } |
| 577 | 571 | ||
| 578 | void BufferCacheRuntime::ReserveNullBuffer() { | 572 | void BufferCacheRuntime::ReserveNullBuffer() { |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 92d3e9f32..cdeef8846 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h | |||
| @@ -97,10 +97,12 @@ public: | |||
| 97 | void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); | 97 | void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); |
| 98 | 98 | ||
| 99 | void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); | 99 | void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); |
| 100 | void BindVertexBuffers(VideoCommon::HostBindings& bindings); | 100 | |
| 101 | void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 101 | 102 | ||
| 102 | void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); | 103 | void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); |
| 103 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); | 104 | |
| 105 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 104 | 106 | ||
| 105 | std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, | 107 | std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, |
| 106 | [[maybe_unused]] u32 binding_index, u32 size) { | 108 | [[maybe_unused]] u32 binding_index, u32 size) { |
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 733e70d9d..73e585c2b 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | |||
| @@ -115,7 +115,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 115 | 115 | ||
| 116 | static constexpr size_t max_elements = 64; | 116 | static constexpr size_t max_elements = 64; |
| 117 | boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; | 117 | boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; |
| 118 | boost::container::static_vector<VkSampler, max_elements> samplers; | 118 | boost::container::static_vector<VideoCommon::SamplerId, max_elements> samplers; |
| 119 | 119 | ||
| 120 | const auto& qmd{kepler_compute.launch_description}; | 120 | const auto& qmd{kepler_compute.launch_description}; |
| 121 | const auto& cbufs{qmd.const_buffer_config}; | 121 | const auto& cbufs{qmd.const_buffer_config}; |
| @@ -160,8 +160,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 160 | const auto handle{read_handle(desc, index)}; | 160 | const auto handle{read_handle(desc, index)}; |
| 161 | views.push_back({handle.first}); | 161 | views.push_back({handle.first}); |
| 162 | 162 | ||
| 163 | Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); | 163 | VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); |
| 164 | samplers.push_back(sampler->Handle()); | 164 | samplers.push_back(sampler); |
| 165 | } | 165 | } |
| 166 | } | 166 | } |
| 167 | for (const auto& desc : info.image_descriptors) { | 167 | for (const auto& desc : info.image_descriptors) { |
| @@ -192,7 +192,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 192 | buffer_cache.BindHostComputeBuffers(); | 192 | buffer_cache.BindHostComputeBuffers(); |
| 193 | 193 | ||
| 194 | RescalingPushConstant rescaling; | 194 | RescalingPushConstant rescaling; |
| 195 | const VkSampler* samplers_it{samplers.data()}; | 195 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; |
| 196 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 196 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 197 | PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, | 197 | PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, |
| 198 | views_it); | 198 | views_it); |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 506b78f08..c1595642e 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -298,7 +298,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) { | |||
| 298 | template <typename Spec> | 298 | template <typename Spec> |
| 299 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | 299 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { |
| 300 | std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; | 300 | std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; |
| 301 | std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers; | 301 | std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers; |
| 302 | size_t sampler_index{}; | 302 | size_t sampler_index{}; |
| 303 | size_t view_index{}; | 303 | size_t view_index{}; |
| 304 | 304 | ||
| @@ -367,8 +367,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 367 | const auto handle{read_handle(desc, index)}; | 367 | const auto handle{read_handle(desc, index)}; |
| 368 | views[view_index++] = {handle.first}; | 368 | views[view_index++] = {handle.first}; |
| 369 | 369 | ||
| 370 | Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; | 370 | VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; |
| 371 | samplers[sampler_index++] = sampler->Handle(); | 371 | samplers[sampler_index++] = sampler; |
| 372 | } | 372 | } |
| 373 | } | 373 | } |
| 374 | if constexpr (Spec::has_images) { | 374 | if constexpr (Spec::has_images) { |
| @@ -453,7 +453,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 453 | 453 | ||
| 454 | RescalingPushConstant rescaling; | 454 | RescalingPushConstant rescaling; |
| 455 | RenderAreaPushConstant render_area; | 455 | RenderAreaPushConstant render_area; |
| 456 | const VkSampler* samplers_it{samplers.data()}; | 456 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; |
| 457 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 457 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 458 | const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { | 458 | const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { |
| 459 | buffer_cache.BindHostStageBuffers(stage); | 459 | buffer_cache.BindHostStageBuffers(stage); |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b128c4f6e..5eeda08d2 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include <thread> | 4 | #include <thread> |
| 5 | 5 | ||
| 6 | #include "common/polyfill_ranges.h" | ||
| 6 | #include "common/settings.h" | 7 | #include "common/settings.h" |
| 7 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" | 8 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" |
| 8 | #include "video_core/vulkan_common/vulkan_device.h" | 9 | #include "video_core/vulkan_common/vulkan_device.h" |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 5734f51e5..a2cfb2105 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -358,6 +358,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 358 | .support_snorm_render_buffer = true, | 358 | .support_snorm_render_buffer = true, |
| 359 | .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), | 359 | .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), |
| 360 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), | 360 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), |
| 361 | .support_conditional_barrier = device.SupportsConditionalBarriers(), | ||
| 361 | }; | 362 | }; |
| 362 | 363 | ||
| 363 | if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { | 364 | if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { |
| @@ -704,10 +705,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( | |||
| 704 | std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( | 705 | std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( |
| 705 | ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, | 706 | ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, |
| 706 | PipelineStatistics* statistics, bool build_in_parallel) try { | 707 | PipelineStatistics* statistics, bool build_in_parallel) try { |
| 707 | // TODO: Remove this when Intel fixes their shader compiler. | 708 | if (device.HasBrokenCompute()) { |
| 708 | // https://github.com/IGCIT/Intel-GPU-Community-Issue-Tracker-IGCIT/issues/159 | ||
| 709 | if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS && | ||
| 710 | !Settings::values.enable_compute_pipelines.GetValue()) { | ||
| 711 | LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash()); | 709 | LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash()); |
| 712 | return nullptr; | 710 | return nullptr; |
| 713 | } | 711 | } |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 15aa7e224..e323ea0fd 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h | |||
| @@ -92,9 +92,9 @@ struct ShaderPools { | |||
| 92 | inst.ReleaseContents(); | 92 | inst.ReleaseContents(); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | Shader::ObjectPool<Shader::IR::Inst> inst; | 95 | Shader::ObjectPool<Shader::IR::Inst> inst{8192}; |
| 96 | Shader::ObjectPool<Shader::IR::Block> block; | 96 | Shader::ObjectPool<Shader::IR::Block> block{32}; |
| 97 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; | 97 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; |
| 98 | }; | 98 | }; |
| 99 | 99 | ||
| 100 | class PipelineCache : public VideoCommon::ShaderCache { | 100 | class PipelineCache : public VideoCommon::ShaderCache { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 8711e2a87..f025f618b 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -1802,27 +1802,36 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t | |||
| 1802 | // Some games have samplers with garbage. Sanitize them here. | 1802 | // Some games have samplers with garbage. Sanitize them here. |
| 1803 | const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); | 1803 | const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); |
| 1804 | 1804 | ||
| 1805 | sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ | 1805 | const auto create_sampler = [&](const f32 anisotropy) { |
| 1806 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, | 1806 | return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ |
| 1807 | .pNext = pnext, | 1807 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, |
| 1808 | .flags = 0, | 1808 | .pNext = pnext, |
| 1809 | .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), | 1809 | .flags = 0, |
| 1810 | .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), | 1810 | .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), |
| 1811 | .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), | 1811 | .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), |
| 1812 | .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), | 1812 | .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), |
| 1813 | .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), | 1813 | .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), |
| 1814 | .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), | 1814 | .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), |
| 1815 | .mipLodBias = tsc.LodBias(), | 1815 | .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), |
| 1816 | .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), | 1816 | .mipLodBias = tsc.LodBias(), |
| 1817 | .maxAnisotropy = max_anisotropy, | 1817 | .anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), |
| 1818 | .compareEnable = tsc.depth_compare_enabled, | 1818 | .maxAnisotropy = anisotropy, |
| 1819 | .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), | 1819 | .compareEnable = tsc.depth_compare_enabled, |
| 1820 | .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), | 1820 | .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), |
| 1821 | .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), | 1821 | .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), |
| 1822 | .borderColor = | 1822 | .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), |
| 1823 | arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), | 1823 | .borderColor = |
| 1824 | .unnormalizedCoordinates = VK_FALSE, | 1824 | arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), |
| 1825 | }); | 1825 | .unnormalizedCoordinates = VK_FALSE, |
| 1826 | }); | ||
| 1827 | }; | ||
| 1828 | |||
| 1829 | sampler = create_sampler(max_anisotropy); | ||
| 1830 | |||
| 1831 | const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy); | ||
| 1832 | if (max_anisotropy > max_anisotropy_default) { | ||
| 1833 | sampler_default_anisotropy = create_sampler(max_anisotropy_default); | ||
| 1834 | } | ||
| 1826 | } | 1835 | } |
| 1827 | 1836 | ||
| 1828 | Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, | 1837 | Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 0f7a5ffd4..f14525dcb 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -279,8 +279,17 @@ public: | |||
| 279 | return *sampler; | 279 | return *sampler; |
| 280 | } | 280 | } |
| 281 | 281 | ||
| 282 | [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { | ||
| 283 | return *sampler_default_anisotropy; | ||
| 284 | } | ||
| 285 | |||
| 286 | [[nodiscard]] bool HasAddedAnisotropy() const noexcept { | ||
| 287 | return static_cast<bool>(sampler_default_anisotropy); | ||
| 288 | } | ||
| 289 | |||
| 282 | private: | 290 | private: |
| 283 | vk::Sampler sampler; | 291 | vk::Sampler sampler; |
| 292 | vk::Sampler sampler_default_anisotropy; | ||
| 284 | }; | 293 | }; |
| 285 | 294 | ||
| 286 | class Framebuffer { | 295 | class Framebuffer { |
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp index d134b6738..0c5f4450d 100644 --- a/src/video_core/texture_cache/image_view_base.cpp +++ b/src/video_core/texture_cache/image_view_base.cpp | |||
| @@ -45,4 +45,56 @@ ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_in | |||
| 45 | 45 | ||
| 46 | ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} | 46 | ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} |
| 47 | 47 | ||
| 48 | bool ImageViewBase::SupportsAnisotropy() const noexcept { | ||
| 49 | const bool has_mips = range.extent.levels > 1; | ||
| 50 | const bool is_2d = type == ImageViewType::e2D || type == ImageViewType::e2DArray; | ||
| 51 | if (!has_mips || !is_2d) { | ||
| 52 | return false; | ||
| 53 | } | ||
| 54 | |||
| 55 | switch (format) { | ||
| 56 | case PixelFormat::R8_UNORM: | ||
| 57 | case PixelFormat::R8_SNORM: | ||
| 58 | case PixelFormat::R8_SINT: | ||
| 59 | case PixelFormat::R8_UINT: | ||
| 60 | case PixelFormat::BC4_UNORM: | ||
| 61 | case PixelFormat::BC4_SNORM: | ||
| 62 | case PixelFormat::BC5_UNORM: | ||
| 63 | case PixelFormat::BC5_SNORM: | ||
| 64 | case PixelFormat::R32G32_FLOAT: | ||
| 65 | case PixelFormat::R32G32_SINT: | ||
| 66 | case PixelFormat::R32_FLOAT: | ||
| 67 | case PixelFormat::R16_FLOAT: | ||
| 68 | case PixelFormat::R16_UNORM: | ||
| 69 | case PixelFormat::R16_SNORM: | ||
| 70 | case PixelFormat::R16_UINT: | ||
| 71 | case PixelFormat::R16_SINT: | ||
| 72 | case PixelFormat::R16G16_UNORM: | ||
| 73 | case PixelFormat::R16G16_FLOAT: | ||
| 74 | case PixelFormat::R16G16_UINT: | ||
| 75 | case PixelFormat::R16G16_SINT: | ||
| 76 | case PixelFormat::R16G16_SNORM: | ||
| 77 | case PixelFormat::R8G8_UNORM: | ||
| 78 | case PixelFormat::R8G8_SNORM: | ||
| 79 | case PixelFormat::R8G8_SINT: | ||
| 80 | case PixelFormat::R8G8_UINT: | ||
| 81 | case PixelFormat::R32G32_UINT: | ||
| 82 | case PixelFormat::R32_UINT: | ||
| 83 | case PixelFormat::R32_SINT: | ||
| 84 | case PixelFormat::G4R4_UNORM: | ||
| 85 | // Depth formats | ||
| 86 | case PixelFormat::D32_FLOAT: | ||
| 87 | case PixelFormat::D16_UNORM: | ||
| 88 | // Stencil formats | ||
| 89 | case PixelFormat::S8_UINT: | ||
| 90 | // DepthStencil formats | ||
| 91 | case PixelFormat::D24_UNORM_S8_UINT: | ||
| 92 | case PixelFormat::S8_UINT_D24_UNORM: | ||
| 93 | case PixelFormat::D32_FLOAT_S8_UINT: | ||
| 94 | return false; | ||
| 95 | default: | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 48 | } // namespace VideoCommon | 100 | } // namespace VideoCommon |
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h index a25ae1d4a..87549ffff 100644 --- a/src/video_core/texture_cache/image_view_base.h +++ b/src/video_core/texture_cache/image_view_base.h | |||
| @@ -33,6 +33,8 @@ struct ImageViewBase { | |||
| 33 | return type == ImageViewType::Buffer; | 33 | return type == ImageViewType::Buffer; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | [[nodiscard]] bool SupportsAnisotropy() const noexcept; | ||
| 37 | |||
| 36 | ImageId image_id{}; | 38 | ImageId image_id{}; |
| 37 | GPUVAddr gpu_addr = 0; | 39 | GPUVAddr gpu_addr = 0; |
| 38 | PixelFormat format{}; | 40 | PixelFormat format{}; |
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 43b7ac0a6..d58bb69ff 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -226,30 +226,50 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) { | |||
| 226 | 226 | ||
| 227 | template <class P> | 227 | template <class P> |
| 228 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { | 228 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { |
| 229 | return &slot_samplers[GetGraphicsSamplerId(index)]; | ||
| 230 | } | ||
| 231 | |||
| 232 | template <class P> | ||
| 233 | typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { | ||
| 234 | return &slot_samplers[GetComputeSamplerId(index)]; | ||
| 235 | } | ||
| 236 | |||
| 237 | template <class P> | ||
| 238 | SamplerId TextureCache<P>::GetGraphicsSamplerId(u32 index) { | ||
| 229 | if (index > channel_state->graphics_sampler_table.Limit()) { | 239 | if (index > channel_state->graphics_sampler_table.Limit()) { |
| 230 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); | 240 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); |
| 231 | return &slot_samplers[NULL_SAMPLER_ID]; | 241 | return NULL_SAMPLER_ID; |
| 232 | } | 242 | } |
| 233 | const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); | 243 | const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); |
| 234 | SamplerId& id = channel_state->graphics_sampler_ids[index]; | 244 | SamplerId& id = channel_state->graphics_sampler_ids[index]; |
| 235 | if (is_new) { | 245 | if (is_new) { |
| 236 | id = FindSampler(descriptor); | 246 | id = FindSampler(descriptor); |
| 237 | } | 247 | } |
| 238 | return &slot_samplers[id]; | 248 | return id; |
| 239 | } | 249 | } |
| 240 | 250 | ||
| 241 | template <class P> | 251 | template <class P> |
| 242 | typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { | 252 | SamplerId TextureCache<P>::GetComputeSamplerId(u32 index) { |
| 243 | if (index > channel_state->compute_sampler_table.Limit()) { | 253 | if (index > channel_state->compute_sampler_table.Limit()) { |
| 244 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); | 254 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); |
| 245 | return &slot_samplers[NULL_SAMPLER_ID]; | 255 | return NULL_SAMPLER_ID; |
| 246 | } | 256 | } |
| 247 | const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); | 257 | const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); |
| 248 | SamplerId& id = channel_state->compute_sampler_ids[index]; | 258 | SamplerId& id = channel_state->compute_sampler_ids[index]; |
| 249 | if (is_new) { | 259 | if (is_new) { |
| 250 | id = FindSampler(descriptor); | 260 | id = FindSampler(descriptor); |
| 251 | } | 261 | } |
| 252 | return &slot_samplers[id]; | 262 | return id; |
| 263 | } | ||
| 264 | |||
| 265 | template <class P> | ||
| 266 | const typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) const noexcept { | ||
| 267 | return slot_samplers[id]; | ||
| 268 | } | ||
| 269 | |||
| 270 | template <class P> | ||
| 271 | typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept { | ||
| 272 | return slot_samplers[id]; | ||
| 253 | } | 273 | } |
| 254 | 274 | ||
| 255 | template <class P> | 275 | template <class P> |
| @@ -284,7 +304,7 @@ void TextureCache<P>::SynchronizeComputeDescriptors() { | |||
| 284 | } | 304 | } |
| 285 | 305 | ||
| 286 | template <class P> | 306 | template <class P> |
| 287 | bool TextureCache<P>::RescaleRenderTargets(bool is_clear) { | 307 | bool TextureCache<P>::RescaleRenderTargets() { |
| 288 | auto& flags = maxwell3d->dirty.flags; | 308 | auto& flags = maxwell3d->dirty.flags; |
| 289 | u32 scale_rating = 0; | 309 | u32 scale_rating = 0; |
| 290 | bool rescaled = false; | 310 | bool rescaled = false; |
| @@ -322,13 +342,13 @@ bool TextureCache<P>::RescaleRenderTargets(bool is_clear) { | |||
| 322 | ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; | 342 | ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; |
| 323 | if (flags[Dirty::ColorBuffer0 + index] || force) { | 343 | if (flags[Dirty::ColorBuffer0 + index] || force) { |
| 324 | flags[Dirty::ColorBuffer0 + index] = false; | 344 | flags[Dirty::ColorBuffer0 + index] = false; |
| 325 | BindRenderTarget(&color_buffer_id, FindColorBuffer(index, is_clear)); | 345 | BindRenderTarget(&color_buffer_id, FindColorBuffer(index)); |
| 326 | } | 346 | } |
| 327 | check_rescale(color_buffer_id, tmp_color_images[index]); | 347 | check_rescale(color_buffer_id, tmp_color_images[index]); |
| 328 | } | 348 | } |
| 329 | if (flags[Dirty::ZetaBuffer] || force) { | 349 | if (flags[Dirty::ZetaBuffer] || force) { |
| 330 | flags[Dirty::ZetaBuffer] = false; | 350 | flags[Dirty::ZetaBuffer] = false; |
| 331 | BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer(is_clear)); | 351 | BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer()); |
| 332 | } | 352 | } |
| 333 | check_rescale(render_targets.depth_buffer_id, tmp_depth_image); | 353 | check_rescale(render_targets.depth_buffer_id, tmp_depth_image); |
| 334 | 354 | ||
| @@ -393,7 +413,7 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) { | |||
| 393 | return; | 413 | return; |
| 394 | } | 414 | } |
| 395 | 415 | ||
| 396 | const bool rescaled = RescaleRenderTargets(is_clear); | 416 | const bool rescaled = RescaleRenderTargets(); |
| 397 | if (is_rescaling != rescaled) { | 417 | if (is_rescaling != rescaled) { |
| 398 | flags[Dirty::RescaleViewports] = true; | 418 | flags[Dirty::RescaleViewports] = true; |
| 399 | flags[Dirty::RescaleScissors] = true; | 419 | flags[Dirty::RescaleScissors] = true; |
| @@ -1662,7 +1682,7 @@ SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) { | |||
| 1662 | } | 1682 | } |
| 1663 | 1683 | ||
| 1664 | template <class P> | 1684 | template <class P> |
| 1665 | ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) { | 1685 | ImageViewId TextureCache<P>::FindColorBuffer(size_t index) { |
| 1666 | const auto& regs = maxwell3d->regs; | 1686 | const auto& regs = maxwell3d->regs; |
| 1667 | if (index >= regs.rt_control.count) { | 1687 | if (index >= regs.rt_control.count) { |
| 1668 | return ImageViewId{}; | 1688 | return ImageViewId{}; |
| @@ -1676,11 +1696,11 @@ ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) { | |||
| 1676 | return ImageViewId{}; | 1696 | return ImageViewId{}; |
| 1677 | } | 1697 | } |
| 1678 | const ImageInfo info(regs.rt[index], regs.anti_alias_samples_mode); | 1698 | const ImageInfo info(regs.rt[index], regs.anti_alias_samples_mode); |
| 1679 | return FindRenderTargetView(info, gpu_addr, is_clear); | 1699 | return FindRenderTargetView(info, gpu_addr); |
| 1680 | } | 1700 | } |
| 1681 | 1701 | ||
| 1682 | template <class P> | 1702 | template <class P> |
| 1683 | ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) { | 1703 | ImageViewId TextureCache<P>::FindDepthBuffer() { |
| 1684 | const auto& regs = maxwell3d->regs; | 1704 | const auto& regs = maxwell3d->regs; |
| 1685 | if (!regs.zeta_enable) { | 1705 | if (!regs.zeta_enable) { |
| 1686 | return ImageViewId{}; | 1706 | return ImageViewId{}; |
| @@ -1690,18 +1710,16 @@ ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) { | |||
| 1690 | return ImageViewId{}; | 1710 | return ImageViewId{}; |
| 1691 | } | 1711 | } |
| 1692 | const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode); | 1712 | const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode); |
| 1693 | return FindRenderTargetView(info, gpu_addr, is_clear); | 1713 | return FindRenderTargetView(info, gpu_addr); |
| 1694 | } | 1714 | } |
| 1695 | 1715 | ||
| 1696 | template <class P> | 1716 | template <class P> |
| 1697 | ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, | 1717 | ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr) { |
| 1698 | bool is_clear) { | ||
| 1699 | const auto options = is_clear ? RelaxedOptions::Samples : RelaxedOptions{}; | ||
| 1700 | ImageId image_id{}; | 1718 | ImageId image_id{}; |
| 1701 | bool delete_state = has_deleted_images; | 1719 | bool delete_state = has_deleted_images; |
| 1702 | do { | 1720 | do { |
| 1703 | has_deleted_images = false; | 1721 | has_deleted_images = false; |
| 1704 | image_id = FindOrInsertImage(info, gpu_addr, options); | 1722 | image_id = FindOrInsertImage(info, gpu_addr); |
| 1705 | delete_state |= has_deleted_images; | 1723 | delete_state |= has_deleted_images; |
| 1706 | } while (has_deleted_images); | 1724 | } while (has_deleted_images); |
| 1707 | has_deleted_images = delete_state; | 1725 | has_deleted_images = delete_state; |
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 3bfa92154..44232b961 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h | |||
| @@ -159,6 +159,18 @@ public: | |||
| 159 | /// Get the sampler from the compute descriptor table in the specified index | 159 | /// Get the sampler from the compute descriptor table in the specified index |
| 160 | Sampler* GetComputeSampler(u32 index); | 160 | Sampler* GetComputeSampler(u32 index); |
| 161 | 161 | ||
| 162 | /// Get the sampler id from the graphics descriptor table in the specified index | ||
| 163 | SamplerId GetGraphicsSamplerId(u32 index); | ||
| 164 | |||
| 165 | /// Get the sampler id from the compute descriptor table in the specified index | ||
| 166 | SamplerId GetComputeSamplerId(u32 index); | ||
| 167 | |||
| 168 | /// Return a constant reference to the given sampler id | ||
| 169 | [[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept; | ||
| 170 | |||
| 171 | /// Return a reference to the given sampler id | ||
| 172 | [[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept; | ||
| 173 | |||
| 162 | /// Refresh the state for graphics image view and sampler descriptors | 174 | /// Refresh the state for graphics image view and sampler descriptors |
| 163 | void SynchronizeGraphicsDescriptors(); | 175 | void SynchronizeGraphicsDescriptors(); |
| 164 | 176 | ||
| @@ -166,9 +178,8 @@ public: | |||
| 166 | void SynchronizeComputeDescriptors(); | 178 | void SynchronizeComputeDescriptors(); |
| 167 | 179 | ||
| 168 | /// Updates the Render Targets if they can be rescaled | 180 | /// Updates the Render Targets if they can be rescaled |
| 169 | /// @param is_clear True when the render targets are being used for clears | ||
| 170 | /// @retval True if the Render Targets have been rescaled. | 181 | /// @retval True if the Render Targets have been rescaled. |
| 171 | bool RescaleRenderTargets(bool is_clear); | 182 | bool RescaleRenderTargets(); |
| 172 | 183 | ||
| 173 | /// Update bound render targets and upload memory if necessary | 184 | /// Update bound render targets and upload memory if necessary |
| 174 | /// @param is_clear True when the render targets are being used for clears | 185 | /// @param is_clear True when the render targets are being used for clears |
| @@ -324,14 +335,13 @@ private: | |||
| 324 | [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); | 335 | [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); |
| 325 | 336 | ||
| 326 | /// Find or create an image view for the given color buffer index | 337 | /// Find or create an image view for the given color buffer index |
| 327 | [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); | 338 | [[nodiscard]] ImageViewId FindColorBuffer(size_t index); |
| 328 | 339 | ||
| 329 | /// Find or create an image view for the depth buffer | 340 | /// Find or create an image view for the depth buffer |
| 330 | [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); | 341 | [[nodiscard]] ImageViewId FindDepthBuffer(); |
| 331 | 342 | ||
| 332 | /// Find or create a view for a render target with the given image parameters | 343 | /// Find or create a view for a render target with the given image parameters |
| 333 | [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, | 344 | [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr); |
| 334 | bool is_clear); | ||
| 335 | 345 | ||
| 336 | /// Iterates over all the images in a region calling func | 346 | /// Iterates over all the images in a region calling func |
| 337 | template <typename Func> | 347 | template <typename Func> |
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index 4a80a59f9..d8b88d9bc 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp | |||
| @@ -62,7 +62,12 @@ std::array<float, 4> TSCEntry::BorderColor() const noexcept { | |||
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | float TSCEntry::MaxAnisotropy() const noexcept { | 64 | float TSCEntry::MaxAnisotropy() const noexcept { |
| 65 | if (max_anisotropy == 0 && mipmap_filter != TextureMipmapFilter::Linear) { | 65 | const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None; |
| 66 | const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256; | ||
| 67 | const bool is_bilinear_filter = min_filter == TextureFilter::Linear && | ||
| 68 | reduction_filter == SamplerReduction::WeightedAverage; | ||
| 69 | if (max_anisotropy == 0 && (!is_suitable_mipmap_filter || !has_regular_lods || | ||
| 70 | !is_bilinear_filter || depth_compare_enabled)) { | ||
| 66 | return 1.0f; | 71 | return 1.0f; |
| 67 | } | 72 | } |
| 68 | const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); | 73 | const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index a46f9beed..fa9cde75b 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -344,6 +344,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 344 | const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; | 344 | const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; |
| 345 | const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; | 345 | const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; |
| 346 | const bool is_s8gen2 = device_id == 0x43050a01; | 346 | const bool is_s8gen2 = device_id == 0x43050a01; |
| 347 | const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; | ||
| 347 | 348 | ||
| 348 | if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) { | 349 | if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) { |
| 349 | LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway"); | 350 | LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway"); |
| @@ -391,7 +392,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 391 | CollectPhysicalMemoryInfo(); | 392 | CollectPhysicalMemoryInfo(); |
| 392 | CollectToolingInfo(); | 393 | CollectToolingInfo(); |
| 393 | 394 | ||
| 394 | #ifdef ANDROID | ||
| 395 | if (is_qualcomm || is_turnip) { | 395 | if (is_qualcomm || is_turnip) { |
| 396 | LOG_WARNING(Render_Vulkan, | 396 | LOG_WARNING(Render_Vulkan, |
| 397 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); | 397 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); |
| @@ -411,7 +411,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 411 | extensions.push_descriptor = false; | 411 | extensions.push_descriptor = false; |
| 412 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | 412 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 413 | 413 | ||
| 414 | #ifdef ARCHITECTURE_arm64 | 414 | #if defined(ANDROID) && defined(ARCHITECTURE_arm64) |
| 415 | // Patch the driver to enable BCn textures. | 415 | // Patch the driver to enable BCn textures. |
| 416 | const auto major = (properties.properties.driverVersion >> 24) << 2; | 416 | const auto major = (properties.properties.driverVersion >> 24) << 2; |
| 417 | const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; | 417 | const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; |
| @@ -431,18 +431,23 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 431 | } else { | 431 | } else { |
| 432 | LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures"); | 432 | LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures"); |
| 433 | } | 433 | } |
| 434 | #endif // ARCHITECTURE_arm64 | 434 | #endif |
| 435 | } | 435 | } |
| 436 | 436 | ||
| 437 | const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; | ||
| 438 | if (is_arm) { | 437 | if (is_arm) { |
| 439 | must_emulate_scaled_formats = true; | 438 | must_emulate_scaled_formats = true; |
| 440 | 439 | ||
| 441 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); | 440 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); |
| 442 | extensions.extended_dynamic_state = false; | 441 | extensions.extended_dynamic_state = false; |
| 443 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 442 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); |
| 443 | |||
| 444 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); | ||
| 445 | features.extended_dynamic_state2.extendedDynamicState2 = false; | ||
| 446 | features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; | ||
| 447 | features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; | ||
| 448 | extensions.extended_dynamic_state2 = false; | ||
| 449 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 444 | } | 450 | } |
| 445 | #endif // ANDROID | ||
| 446 | 451 | ||
| 447 | if (is_nvidia) { | 452 | if (is_nvidia) { |
| 448 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; | 453 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; |
| @@ -557,6 +562,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 557 | LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits"); | 562 | LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits"); |
| 558 | cant_blit_msaa = true; | 563 | cant_blit_msaa = true; |
| 559 | } | 564 | } |
| 565 | has_broken_compute = | ||
| 566 | CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) && | ||
| 567 | !Settings::values.enable_compute_pipelines.GetValue(); | ||
| 560 | if (is_intel_anv || (is_qualcomm && !is_s8gen2)) { | 568 | if (is_intel_anv || (is_qualcomm && !is_s8gen2)) { |
| 561 | LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format"); | 569 | LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format"); |
| 562 | must_emulate_bgr565 = true; | 570 | must_emulate_bgr565 = true; |
| @@ -778,9 +786,6 @@ bool Device::GetSuitability(bool requires_swapchain) { | |||
| 778 | 786 | ||
| 779 | FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); | 787 | FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); |
| 780 | FOR_EACH_VK_EXTENSION(EXTENSION); | 788 | FOR_EACH_VK_EXTENSION(EXTENSION); |
| 781 | #ifdef _WIN32 | ||
| 782 | FOR_EACH_VK_EXTENSION_WIN32(EXTENSION); | ||
| 783 | #endif | ||
| 784 | 789 | ||
| 785 | #undef FEATURE_EXTENSION | 790 | #undef FEATURE_EXTENSION |
| 786 | #undef EXTENSION | 791 | #undef EXTENSION |
| @@ -799,11 +804,6 @@ bool Device::GetSuitability(bool requires_swapchain) { | |||
| 799 | 804 | ||
| 800 | FOR_EACH_VK_RECOMMENDED_EXTENSION(LOG_EXTENSION); | 805 | FOR_EACH_VK_RECOMMENDED_EXTENSION(LOG_EXTENSION); |
| 801 | FOR_EACH_VK_MANDATORY_EXTENSION(CHECK_EXTENSION); | 806 | FOR_EACH_VK_MANDATORY_EXTENSION(CHECK_EXTENSION); |
| 802 | #ifdef _WIN32 | ||
| 803 | FOR_EACH_VK_MANDATORY_EXTENSION_WIN32(CHECK_EXTENSION); | ||
| 804 | #else | ||
| 805 | FOR_EACH_VK_MANDATORY_EXTENSION_GENERIC(CHECK_EXTENSION); | ||
| 806 | #endif | ||
| 807 | 807 | ||
| 808 | if (requires_swapchain) { | 808 | if (requires_swapchain) { |
| 809 | CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); | 809 | CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); |
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index f314d0ffe..0b634a876 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include <vector> | 10 | #include <vector> |
| 11 | 11 | ||
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "common/logging/log.h" | ||
| 13 | #include "common/settings.h" | 14 | #include "common/settings.h" |
| 14 | #include "video_core/vulkan_common/vulkan_wrapper.h" | 15 | #include "video_core/vulkan_common/vulkan_wrapper.h" |
| 15 | 16 | ||
| @@ -68,7 +69,6 @@ | |||
| 68 | EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \ | 69 | EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \ |
| 69 | EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \ | 70 | EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \ |
| 70 | EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \ | 71 | EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \ |
| 71 | EXTENSION(KHR, EXTERNAL_MEMORY_FD, external_memory_fd) \ | ||
| 72 | EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \ | 72 | EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \ |
| 73 | EXTENSION(KHR, SAMPLER_MIRROR_CLAMP_TO_EDGE, sampler_mirror_clamp_to_edge) \ | 73 | EXTENSION(KHR, SAMPLER_MIRROR_CLAMP_TO_EDGE, sampler_mirror_clamp_to_edge) \ |
| 74 | EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \ | 74 | EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \ |
| @@ -80,9 +80,6 @@ | |||
| 80 | EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \ | 80 | EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \ |
| 81 | EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) | 81 | EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) |
| 82 | 82 | ||
| 83 | #define FOR_EACH_VK_EXTENSION_WIN32(EXTENSION) \ | ||
| 84 | EXTENSION(KHR, EXTERNAL_MEMORY_WIN32, external_memory_win32) | ||
| 85 | |||
| 86 | // Define extensions which must be supported. | 83 | // Define extensions which must be supported. |
| 87 | #define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \ | 84 | #define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \ |
| 88 | EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \ | 85 | EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \ |
| @@ -90,12 +87,6 @@ | |||
| 90 | EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \ | 87 | EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \ |
| 91 | EXTENSION_NAME(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) | 88 | EXTENSION_NAME(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) |
| 92 | 89 | ||
| 93 | #define FOR_EACH_VK_MANDATORY_EXTENSION_GENERIC(EXTENSION_NAME) \ | ||
| 94 | EXTENSION_NAME(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) | ||
| 95 | |||
| 96 | #define FOR_EACH_VK_MANDATORY_EXTENSION_WIN32(EXTENSION_NAME) \ | ||
| 97 | EXTENSION_NAME(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME) | ||
| 98 | |||
| 99 | // Define extensions where the absence of the extension may result in a degraded experience. | 90 | // Define extensions where the absence of the extension may result in a degraded experience. |
| 100 | #define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ | 91 | #define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ |
| 101 | EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ | 92 | EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ |
| @@ -528,6 +519,11 @@ public: | |||
| 528 | return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue(); | 519 | return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue(); |
| 529 | } | 520 | } |
| 530 | 521 | ||
| 522 | /// @returns True if compute pipelines can cause crashing. | ||
| 523 | bool HasBrokenCompute() const { | ||
| 524 | return has_broken_compute; | ||
| 525 | } | ||
| 526 | |||
| 531 | /// Returns true when the device does not properly support cube compatibility. | 527 | /// Returns true when the device does not properly support cube compatibility. |
| 532 | bool HasBrokenCubeImageCompability() const { | 528 | bool HasBrokenCubeImageCompability() const { |
| 533 | return has_broken_cube_compatibility; | 529 | return has_broken_cube_compatibility; |
| @@ -589,6 +585,22 @@ public: | |||
| 589 | return supports_conditional_barriers; | 585 | return supports_conditional_barriers; |
| 590 | } | 586 | } |
| 591 | 587 | ||
| 588 | [[nodiscard]] static constexpr bool CheckBrokenCompute(VkDriverId driver_id, | ||
| 589 | u32 driver_version) { | ||
| 590 | if (driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { | ||
| 591 | const u32 major = VK_API_VERSION_MAJOR(driver_version); | ||
| 592 | const u32 minor = VK_API_VERSION_MINOR(driver_version); | ||
| 593 | const u32 patch = VK_API_VERSION_PATCH(driver_version); | ||
| 594 | if (major == 0 && minor == 405 && patch < 286) { | ||
| 595 | LOG_WARNING( | ||
| 596 | Render_Vulkan, | ||
| 597 | "Intel proprietary drivers 0.405.0 until 0.405.286 have broken compute"); | ||
| 598 | return true; | ||
| 599 | } | ||
| 600 | } | ||
| 601 | return false; | ||
| 602 | } | ||
| 603 | |||
| 592 | private: | 604 | private: |
| 593 | /// Checks if the physical device is suitable and configures the object state | 605 | /// Checks if the physical device is suitable and configures the object state |
| 594 | /// with all necessary info about its properties. | 606 | /// with all necessary info about its properties. |
| @@ -636,7 +648,6 @@ private: | |||
| 636 | FOR_EACH_VK_FEATURE_1_3(FEATURE); | 648 | FOR_EACH_VK_FEATURE_1_3(FEATURE); |
| 637 | FOR_EACH_VK_FEATURE_EXT(FEATURE); | 649 | FOR_EACH_VK_FEATURE_EXT(FEATURE); |
| 638 | FOR_EACH_VK_EXTENSION(EXTENSION); | 650 | FOR_EACH_VK_EXTENSION(EXTENSION); |
| 639 | FOR_EACH_VK_EXTENSION_WIN32(EXTENSION); | ||
| 640 | 651 | ||
| 641 | #undef EXTENSION | 652 | #undef EXTENSION |
| 642 | #undef FEATURE | 653 | #undef FEATURE |
| @@ -683,6 +694,7 @@ private: | |||
| 683 | bool is_integrated{}; ///< Is GPU an iGPU. | 694 | bool is_integrated{}; ///< Is GPU an iGPU. |
| 684 | bool is_virtual{}; ///< Is GPU a virtual GPU. | 695 | bool is_virtual{}; ///< Is GPU a virtual GPU. |
| 685 | bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. | 696 | bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. |
| 697 | bool has_broken_compute{}; ///< Compute shaders can cause crashes | ||
| 686 | bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit | 698 | bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit |
| 687 | bool has_renderdoc{}; ///< Has RenderDoc attached | 699 | bool has_renderdoc{}; ///< Has RenderDoc attached |
| 688 | bool has_nsight_graphics{}; ///< Has Nsight Graphics attached | 700 | bool has_nsight_graphics{}; ///< Has Nsight Graphics attached |
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 84d9ca796..733c296e4 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -210,6 +210,8 @@ add_executable(yuzu | |||
| 210 | util/url_request_interceptor.h | 210 | util/url_request_interceptor.h |
| 211 | util/util.cpp | 211 | util/util.cpp |
| 212 | util/util.h | 212 | util/util.h |
| 213 | vk_device_info.cpp | ||
| 214 | vk_device_info.h | ||
| 213 | compatdb.cpp | 215 | compatdb.cpp |
| 214 | compatdb.h | 216 | compatdb.h |
| 215 | yuzu.qrc | 217 | yuzu.qrc |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 8e76a819a..bdf83ebfe 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include "common/settings.h" | 6 | #include "common/settings.h" |
| 7 | #include "core/core.h" | 7 | #include "core/core.h" |
| 8 | #include "ui_configure.h" | 8 | #include "ui_configure.h" |
| 9 | #include "vk_device_info.h" | ||
| 9 | #include "yuzu/configuration/config.h" | 10 | #include "yuzu/configuration/config.h" |
| 10 | #include "yuzu/configuration/configure_audio.h" | 11 | #include "yuzu/configuration/configure_audio.h" |
| 11 | #include "yuzu/configuration/configure_cpu.h" | 12 | #include "yuzu/configuration/configure_cpu.h" |
| @@ -28,6 +29,7 @@ | |||
| 28 | 29 | ||
| 29 | ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, | 30 | ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, |
| 30 | InputCommon::InputSubsystem* input_subsystem, | 31 | InputCommon::InputSubsystem* input_subsystem, |
| 32 | std::vector<VkDeviceInfo::Record>& vk_device_records, | ||
| 31 | Core::System& system_, bool enable_web_config) | 33 | Core::System& system_, bool enable_web_config) |
| 32 | : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, | 34 | : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, |
| 33 | registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, | 35 | registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, |
| @@ -38,7 +40,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, | |||
| 38 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, | 40 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, |
| 39 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, | 41 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, |
| 40 | graphics_tab{std::make_unique<ConfigureGraphics>( | 42 | graphics_tab{std::make_unique<ConfigureGraphics>( |
| 41 | system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this)}, | 43 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, |
| 44 | this)}, | ||
| 42 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, | 45 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, |
| 43 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, | 46 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, |
| 44 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, | 47 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, |
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index a086a07c4..2a08b7fee 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h | |||
| @@ -4,7 +4,9 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <vector> | ||
| 7 | #include <QDialog> | 8 | #include <QDialog> |
| 9 | #include "yuzu/vk_device_info.h" | ||
| 8 | 10 | ||
| 9 | namespace Core { | 11 | namespace Core { |
| 10 | class System; | 12 | class System; |
| @@ -40,8 +42,9 @@ class ConfigureDialog : public QDialog { | |||
| 40 | 42 | ||
| 41 | public: | 43 | public: |
| 42 | explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, | 44 | explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, |
| 43 | InputCommon::InputSubsystem* input_subsystem, Core::System& system_, | 45 | InputCommon::InputSubsystem* input_subsystem, |
| 44 | bool enable_web_config = true); | 46 | std::vector<VkDeviceInfo::Record>& vk_device_records, |
| 47 | Core::System& system_, bool enable_web_config = true); | ||
| 45 | ~ConfigureDialog() override; | 48 | ~ConfigureDialog() override; |
| 46 | 49 | ||
| 47 | void ApplyConfiguration(); | 50 | void ApplyConfiguration(); |
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 431585216..a4965524a 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp | |||
| @@ -1,10 +1,6 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2016 Citra Emulator Project | 1 | // SPDX-FileCopyrightText: 2016 Citra Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | // Include this early to include Vulkan headers how we want to | ||
| 5 | #include "video_core/vulkan_common/vulkan_device.h" | ||
| 6 | #include "video_core/vulkan_common/vulkan_wrapper.h" | ||
| 7 | |||
| 8 | #include <algorithm> | 4 | #include <algorithm> |
| 9 | #include <functional> | 5 | #include <functional> |
| 10 | #include <iosfwd> | 6 | #include <iosfwd> |
| @@ -34,13 +30,11 @@ | |||
| 34 | #include "common/settings.h" | 30 | #include "common/settings.h" |
| 35 | #include "core/core.h" | 31 | #include "core/core.h" |
| 36 | #include "ui_configure_graphics.h" | 32 | #include "ui_configure_graphics.h" |
| 37 | #include "video_core/vulkan_common/vulkan_instance.h" | ||
| 38 | #include "video_core/vulkan_common/vulkan_library.h" | ||
| 39 | #include "video_core/vulkan_common/vulkan_surface.h" | ||
| 40 | #include "yuzu/configuration/configuration_shared.h" | 33 | #include "yuzu/configuration/configuration_shared.h" |
| 41 | #include "yuzu/configuration/configure_graphics.h" | 34 | #include "yuzu/configuration/configure_graphics.h" |
| 42 | #include "yuzu/qt_common.h" | 35 | #include "yuzu/qt_common.h" |
| 43 | #include "yuzu/uisettings.h" | 36 | #include "yuzu/uisettings.h" |
| 37 | #include "yuzu/vk_device_info.h" | ||
| 44 | 38 | ||
| 45 | static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, | 39 | static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, |
| 46 | VK_PRESENT_MODE_FIFO_KHR}; | 40 | VK_PRESENT_MODE_FIFO_KHR}; |
| @@ -77,9 +71,10 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) | |||
| 77 | } | 71 | } |
| 78 | 72 | ||
| 79 | ConfigureGraphics::ConfigureGraphics(const Core::System& system_, | 73 | ConfigureGraphics::ConfigureGraphics(const Core::System& system_, |
| 74 | std::vector<VkDeviceInfo::Record>& records_, | ||
| 80 | const std::function<void()>& expose_compute_option_, | 75 | const std::function<void()>& expose_compute_option_, |
| 81 | QWidget* parent) | 76 | QWidget* parent) |
| 82 | : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, | 77 | : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, records{records_}, |
| 83 | expose_compute_option{expose_compute_option_}, system{system_} { | 78 | expose_compute_option{expose_compute_option_}, system{system_} { |
| 84 | vulkan_device = Settings::values.vulkan_device.GetValue(); | 79 | vulkan_device = Settings::values.vulkan_device.GetValue(); |
| 85 | RetrieveVulkanDevices(); | 80 | RetrieveVulkanDevices(); |
| @@ -504,47 +499,19 @@ void ConfigureGraphics::UpdateAPILayout() { | |||
| 504 | } | 499 | } |
| 505 | } | 500 | } |
| 506 | 501 | ||
| 507 | void ConfigureGraphics::RetrieveVulkanDevices() try { | 502 | void ConfigureGraphics::RetrieveVulkanDevices() { |
| 508 | if (UISettings::values.has_broken_vulkan) { | ||
| 509 | return; | ||
| 510 | } | ||
| 511 | |||
| 512 | using namespace Vulkan; | ||
| 513 | |||
| 514 | auto* window = this->window()->windowHandle(); | ||
| 515 | auto wsi = QtCommon::GetWindowSystemInfo(window); | ||
| 516 | |||
| 517 | vk::InstanceDispatch dld; | ||
| 518 | const auto library = OpenLibrary(); | ||
| 519 | const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type); | ||
| 520 | const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); | ||
| 521 | vk::SurfaceKHR surface = CreateSurface(instance, wsi); | ||
| 522 | |||
| 523 | vulkan_devices.clear(); | 503 | vulkan_devices.clear(); |
| 524 | vulkan_devices.reserve(physical_devices.size()); | 504 | vulkan_devices.reserve(records.size()); |
| 525 | device_present_modes.clear(); | 505 | device_present_modes.clear(); |
| 526 | device_present_modes.reserve(physical_devices.size()); | 506 | device_present_modes.reserve(records.size()); |
| 527 | for (const VkPhysicalDevice device : physical_devices) { | 507 | for (const auto& record : records) { |
| 528 | const auto physical_device = vk::PhysicalDevice(device, dld); | 508 | vulkan_devices.push_back(QString::fromStdString(record.name)); |
| 529 | const std::string name = physical_device.GetProperties().deviceName; | 509 | device_present_modes.push_back(record.vsync_support); |
| 530 | const std::vector<VkPresentModeKHR> present_modes = | 510 | |
| 531 | physical_device.GetSurfacePresentModesKHR(*surface); | 511 | if (record.has_broken_compute) { |
| 532 | vulkan_devices.push_back(QString::fromStdString(name)); | ||
| 533 | device_present_modes.push_back(present_modes); | ||
| 534 | |||
| 535 | VkPhysicalDeviceDriverProperties driver_properties{}; | ||
| 536 | driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; | ||
| 537 | driver_properties.pNext = nullptr; | ||
| 538 | VkPhysicalDeviceProperties2 properties{}; | ||
| 539 | properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; | ||
| 540 | properties.pNext = &driver_properties; | ||
| 541 | dld.vkGetPhysicalDeviceProperties2(physical_device, &properties); | ||
| 542 | if (driver_properties.driverID == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { | ||
| 543 | expose_compute_option(); | 512 | expose_compute_option(); |
| 544 | } | 513 | } |
| 545 | } | 514 | } |
| 546 | } catch (const Vulkan::vk::Exception& exception) { | ||
| 547 | LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); | ||
| 548 | } | 515 | } |
| 549 | 516 | ||
| 550 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { | 517 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { |
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 364b1cac2..be9310b74 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include <qobjectdefs.h> | 12 | #include <qobjectdefs.h> |
| 13 | #include <vulkan/vulkan_core.h> | 13 | #include <vulkan/vulkan_core.h> |
| 14 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 15 | #include "vk_device_info.h" | ||
| 15 | 16 | ||
| 16 | class QEvent; | 17 | class QEvent; |
| 17 | class QObject; | 18 | class QObject; |
| @@ -39,6 +40,7 @@ class ConfigureGraphics : public QWidget { | |||
| 39 | 40 | ||
| 40 | public: | 41 | public: |
| 41 | explicit ConfigureGraphics(const Core::System& system_, | 42 | explicit ConfigureGraphics(const Core::System& system_, |
| 43 | std::vector<VkDeviceInfo::Record>& records, | ||
| 42 | const std::function<void()>& expose_compute_option_, | 44 | const std::function<void()>& expose_compute_option_, |
| 43 | QWidget* parent = nullptr); | 45 | QWidget* parent = nullptr); |
| 44 | ~ConfigureGraphics() override; | 46 | ~ConfigureGraphics() override; |
| @@ -77,6 +79,7 @@ private: | |||
| 77 | ConfigurationShared::CheckState use_disk_shader_cache; | 79 | ConfigurationShared::CheckState use_disk_shader_cache; |
| 78 | ConfigurationShared::CheckState use_asynchronous_gpu_emulation; | 80 | ConfigurationShared::CheckState use_asynchronous_gpu_emulation; |
| 79 | 81 | ||
| 82 | std::vector<VkDeviceInfo::Record>& records; | ||
| 80 | std::vector<QString> vulkan_devices; | 83 | std::vector<QString> vulkan_devices; |
| 81 | std::vector<std::vector<VkPresentModeKHR>> device_present_modes; | 84 | std::vector<std::vector<VkPresentModeKHR>> device_present_modes; |
| 82 | std::vector<VkPresentModeKHR> | 85 | std::vector<VkPresentModeKHR> |
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 7ac162586..eb96e6068 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include <utility> | 8 | #include <utility> |
| 9 | #include <vector> | ||
| 9 | 10 | ||
| 10 | #include <fmt/format.h> | 11 | #include <fmt/format.h> |
| 11 | 12 | ||
| @@ -34,8 +35,10 @@ | |||
| 34 | #include "yuzu/configuration/configure_system.h" | 35 | #include "yuzu/configuration/configure_system.h" |
| 35 | #include "yuzu/uisettings.h" | 36 | #include "yuzu/uisettings.h" |
| 36 | #include "yuzu/util/util.h" | 37 | #include "yuzu/util/util.h" |
| 38 | #include "yuzu/vk_device_info.h" | ||
| 37 | 39 | ||
| 38 | ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, | 40 | ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, |
| 41 | std::vector<VkDeviceInfo::Record>& vk_device_records, | ||
| 39 | Core::System& system_) | 42 | Core::System& system_) |
| 40 | : QDialog(parent), | 43 | : QDialog(parent), |
| 41 | ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} { | 44 | ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} { |
| @@ -50,7 +53,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st | |||
| 50 | general_tab = std::make_unique<ConfigureGeneral>(system_, this); | 53 | general_tab = std::make_unique<ConfigureGeneral>(system_, this); |
| 51 | graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this); | 54 | graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this); |
| 52 | graphics_tab = std::make_unique<ConfigureGraphics>( | 55 | graphics_tab = std::make_unique<ConfigureGraphics>( |
| 53 | system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this); | 56 | system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this); |
| 54 | input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); | 57 | input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); |
| 55 | system_tab = std::make_unique<ConfigureSystem>(system_, this); | 58 | system_tab = std::make_unique<ConfigureSystem>(system_, this); |
| 56 | 59 | ||
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index 85752f1fa..7ec1ded06 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h | |||
| @@ -5,11 +5,13 @@ | |||
| 5 | 5 | ||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include <vector> | ||
| 8 | 9 | ||
| 9 | #include <QDialog> | 10 | #include <QDialog> |
| 10 | #include <QList> | 11 | #include <QList> |
| 11 | 12 | ||
| 12 | #include "core/file_sys/vfs_types.h" | 13 | #include "core/file_sys/vfs_types.h" |
| 14 | #include "vk_device_info.h" | ||
| 13 | #include "yuzu/configuration/config.h" | 15 | #include "yuzu/configuration/config.h" |
| 14 | 16 | ||
| 15 | namespace Core { | 17 | namespace Core { |
| @@ -45,6 +47,7 @@ class ConfigurePerGame : public QDialog { | |||
| 45 | public: | 47 | public: |
| 46 | // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 | 48 | // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 |
| 47 | explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, | 49 | explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, |
| 50 | std::vector<VkDeviceInfo::Record>& vk_device_records, | ||
| 48 | Core::System& system_); | 51 | Core::System& system_); |
| 49 | ~ConfigurePerGame() override; | 52 | ~ConfigurePerGame() override; |
| 50 | 53 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 013715b44..8768a7c3c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -147,6 +147,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 147 | #include "yuzu/startup_checks.h" | 147 | #include "yuzu/startup_checks.h" |
| 148 | #include "yuzu/uisettings.h" | 148 | #include "yuzu/uisettings.h" |
| 149 | #include "yuzu/util/clickable_label.h" | 149 | #include "yuzu/util/clickable_label.h" |
| 150 | #include "yuzu/vk_device_info.h" | ||
| 150 | 151 | ||
| 151 | #ifdef YUZU_DBGHELP | 152 | #ifdef YUZU_DBGHELP |
| 152 | #include "yuzu/mini_dump.h" | 153 | #include "yuzu/mini_dump.h" |
| @@ -440,6 +441,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 440 | 441 | ||
| 441 | renderer_status_button->setDisabled(true); | 442 | renderer_status_button->setDisabled(true); |
| 442 | renderer_status_button->setChecked(false); | 443 | renderer_status_button->setChecked(false); |
| 444 | } else { | ||
| 445 | VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle()); | ||
| 443 | } | 446 | } |
| 444 | 447 | ||
| 445 | #if defined(HAVE_SDL2) && !defined(_WIN32) | 448 | #if defined(HAVE_SDL2) && !defined(_WIN32) |
| @@ -3494,7 +3497,8 @@ void GMainWindow::OnConfigure() { | |||
| 3494 | const auto old_language_index = Settings::values.language_index.GetValue(); | 3497 | const auto old_language_index = Settings::values.language_index.GetValue(); |
| 3495 | 3498 | ||
| 3496 | Settings::SetConfiguringGlobal(true); | 3499 | Settings::SetConfiguringGlobal(true); |
| 3497 | ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, | 3500 | ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), |
| 3501 | vk_device_records, *system, | ||
| 3498 | !multiplayer_state->IsHostingPublicRoom()); | 3502 | !multiplayer_state->IsHostingPublicRoom()); |
| 3499 | connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, | 3503 | connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, |
| 3500 | &GMainWindow::OnLanguageChanged); | 3504 | &GMainWindow::OnLanguageChanged); |
| @@ -3765,7 +3769,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file | |||
| 3765 | const auto v_file = Core::GetGameFileFromPath(vfs, file_name); | 3769 | const auto v_file = Core::GetGameFileFromPath(vfs, file_name); |
| 3766 | 3770 | ||
| 3767 | Settings::SetConfiguringGlobal(false); | 3771 | Settings::SetConfiguringGlobal(false); |
| 3768 | ConfigurePerGame dialog(this, title_id, file_name, *system); | 3772 | ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *system); |
| 3769 | dialog.LoadFromFile(v_file); | 3773 | dialog.LoadFromFile(v_file); |
| 3770 | const auto result = dialog.exec(); | 3774 | const auto result = dialog.exec(); |
| 3771 | 3775 | ||
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6bb70972f..e0e775d87 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -118,6 +118,10 @@ enum class ReinitializeKeyBehavior { | |||
| 118 | Warning, | 118 | Warning, |
| 119 | }; | 119 | }; |
| 120 | 120 | ||
| 121 | namespace VkDeviceInfo { | ||
| 122 | class Record; | ||
| 123 | } | ||
| 124 | |||
| 121 | class GMainWindow : public QMainWindow { | 125 | class GMainWindow : public QMainWindow { |
| 122 | Q_OBJECT | 126 | Q_OBJECT |
| 123 | 127 | ||
| @@ -418,6 +422,8 @@ private: | |||
| 418 | 422 | ||
| 419 | GameListPlaceholder* game_list_placeholder; | 423 | GameListPlaceholder* game_list_placeholder; |
| 420 | 424 | ||
| 425 | std::vector<VkDeviceInfo::Record> vk_device_records; | ||
| 426 | |||
| 421 | // Status bar elements | 427 | // Status bar elements |
| 422 | QLabel* message_label = nullptr; | 428 | QLabel* message_label = nullptr; |
| 423 | QLabel* shader_building_label = nullptr; | 429 | QLabel* shader_building_label = nullptr; |
diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp new file mode 100644 index 000000000..7c26a3dc7 --- /dev/null +++ b/src/yuzu/vk_device_info.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <utility> | ||
| 5 | #include <vector> | ||
| 6 | #include "common/dynamic_library.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | #include "video_core/vulkan_common/vulkan_device.h" | ||
| 9 | #include "video_core/vulkan_common/vulkan_instance.h" | ||
| 10 | #include "video_core/vulkan_common/vulkan_library.h" | ||
| 11 | #include "video_core/vulkan_common/vulkan_surface.h" | ||
| 12 | #include "video_core/vulkan_common/vulkan_wrapper.h" | ||
| 13 | #include "vulkan/vulkan_core.h" | ||
| 14 | #include "yuzu/qt_common.h" | ||
| 15 | #include "yuzu/vk_device_info.h" | ||
| 16 | |||
| 17 | class QWindow; | ||
| 18 | |||
| 19 | namespace VkDeviceInfo { | ||
| 20 | Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_, | ||
| 21 | bool has_broken_compute_) | ||
| 22 | : name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {} | ||
| 23 | |||
| 24 | Record::~Record() = default; | ||
| 25 | |||
| 26 | void PopulateRecords(std::vector<Record>& records, QWindow* window) try { | ||
| 27 | using namespace Vulkan; | ||
| 28 | |||
| 29 | auto wsi = QtCommon::GetWindowSystemInfo(window); | ||
| 30 | |||
| 31 | vk::InstanceDispatch dld; | ||
| 32 | const auto library = OpenLibrary(); | ||
| 33 | const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type); | ||
| 34 | const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); | ||
| 35 | vk::SurfaceKHR surface = CreateSurface(instance, wsi); | ||
| 36 | |||
| 37 | records.clear(); | ||
| 38 | records.reserve(physical_devices.size()); | ||
| 39 | for (const VkPhysicalDevice device : physical_devices) { | ||
| 40 | const auto physical_device = vk::PhysicalDevice(device, dld); | ||
| 41 | const std::string name = physical_device.GetProperties().deviceName; | ||
| 42 | const std::vector<VkPresentModeKHR> present_modes = | ||
| 43 | physical_device.GetSurfacePresentModesKHR(*surface); | ||
| 44 | |||
| 45 | VkPhysicalDeviceDriverProperties driver_properties{}; | ||
| 46 | driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; | ||
| 47 | driver_properties.pNext = nullptr; | ||
| 48 | VkPhysicalDeviceProperties2 properties{}; | ||
| 49 | properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; | ||
| 50 | properties.pNext = &driver_properties; | ||
| 51 | dld.vkGetPhysicalDeviceProperties2(physical_device, &properties); | ||
| 52 | |||
| 53 | bool has_broken_compute{Vulkan::Device::CheckBrokenCompute( | ||
| 54 | driver_properties.driverID, properties.properties.driverVersion)}; | ||
| 55 | |||
| 56 | records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute)); | ||
| 57 | } | ||
| 58 | } catch (const Vulkan::vk::Exception& exception) { | ||
| 59 | LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); | ||
| 60 | } | ||
| 61 | } // namespace VkDeviceInfo | ||
diff --git a/src/yuzu/vk_device_info.h b/src/yuzu/vk_device_info.h new file mode 100644 index 000000000..bda8262f4 --- /dev/null +++ b/src/yuzu/vk_device_info.h | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <iterator> | ||
| 8 | #include <memory> | ||
| 9 | #include <string> | ||
| 10 | #include <string_view> | ||
| 11 | #include <vector> | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "vulkan/vulkan_core.h" | ||
| 14 | |||
| 15 | class QWindow; | ||
| 16 | |||
| 17 | namespace Settings { | ||
| 18 | enum class VSyncMode : u32; | ||
| 19 | } | ||
| 20 | // #include "common/settings.h" | ||
| 21 | |||
| 22 | namespace VkDeviceInfo { | ||
| 23 | // Short class to record Vulkan driver information for configuration purposes | ||
| 24 | class Record { | ||
| 25 | public: | ||
| 26 | explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes, | ||
| 27 | bool has_broken_compute); | ||
| 28 | ~Record(); | ||
| 29 | |||
| 30 | const std::string name; | ||
| 31 | const std::vector<VkPresentModeKHR> vsync_support; | ||
| 32 | const bool has_broken_compute; | ||
| 33 | }; | ||
| 34 | |||
| 35 | void PopulateRecords(std::vector<Record>& records, QWindow* window); | ||
| 36 | } // namespace VkDeviceInfo | ||
diff --git a/vcpkg.json b/vcpkg.json index 26f545c6c..7d9e631a1 100644 --- a/vcpkg.json +++ b/vcpkg.json | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | { | 1 | { |
| 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", | 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", |
| 3 | "name": "yuzu", | 3 | "name": "yuzu", |
| 4 | "builtin-baseline": "656fcc6ab2b05c6d999b7eaca717027ac3738f71", | 4 | "builtin-baseline": "cbf56573a987527b39272e88cbdd11389b78c6e4", |
| 5 | "version": "1.0", | 5 | "version": "1.0", |
| 6 | "dependencies": [ | 6 | "dependencies": [ |
| 7 | "boost-algorithm", | 7 | "boost-algorithm", |