diff options
51 files changed, 712 insertions, 328 deletions
diff --git a/.gitmodules b/.gitmodules index 95eae8109..89f2ad924 100644 --- a/.gitmodules +++ b/.gitmodules @@ -52,3 +52,6 @@ [submodule "libadrenotools"] path = externals/libadrenotools url = https://github.com/bylaws/libadrenotools +[submodule "tzdb_to_nx"] + path = externals/nx_tzdb/tzdb_to_nx + 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) option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) +option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF) + CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) # On Android, fetch and compile libcxx before doing anything else diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt index 2f625c108..d5a1c6317 100644 --- a/externals/nx_tzdb/CMakeLists.txt +++ b/externals/nx_tzdb/CMakeLists.txt @@ -1,24 +1,60 @@ # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -set(NX_TZDB_VERSION "220816") -set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip") +set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") + +add_library(nx_tzdb INTERFACE) + +find_program(GIT git) +find_program(GNU_MAKE make) +find_program(GNU_DATE date) +set(CAN_BUILD_NX_TZDB true) + +if (NOT GIT) + set(CAN_BUILD_NX_TZDB false) +endif() +if (NOT GNU_MAKE) + set(CAN_BUILD_NX_TZDB false) +endif() +if (NOT GNU_DATE) + set(CAN_BUILD_NX_TZDB false) +endif() +if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID) + # tzdb_to_nx currently requires a posix-compliant host + # MinGW and Android are handled here due to the executable format being different from the host system + # TODO (lat9nq): cross-compiling support + set(CAN_BUILD_NX_TZDB false) +endif() + +set(NX_TZDB_VERSION "220816") set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") -set(NX_TZDB_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") -set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") +set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") + +if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE}) + set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip") + + message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...") + file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE} + STATUS NX_TZDB_DOWNLOAD_STATUS) + list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE) + if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0) + message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})") + endif() -if (NOT EXISTS ${NX_TZDB_ARCHIVE}) - file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}) file(ARCHIVE_EXTRACT INPUT ${NX_TZDB_ARCHIVE} DESTINATION - ${NX_TZDB_DIR}) + ${NX_TZDB_ROMFS_DIR}) +elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA) + add_subdirectory(tzdb_to_nx) + add_dependencies(nx_tzdb x80e) + + set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}") endif() -add_library(nx_tzdb INTERFACE) target_include_directories(nx_tzdb INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include INTERFACE ${NX_TZDB_INCLUDE_DIR}) @@ -41,25 +77,25 @@ function(CreateHeader ZONE_PATH HEADER_NAME) target_sources(nx_tzdb PRIVATE ${HEADER_PATH}) endfunction() -CreateHeader(${NX_TZDB_DIR} base) -CreateHeader(${NX_TZDB_DIR}/zoneinfo zoneinfo) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Africa africa) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/America america) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Argentina america_argentina) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Indiana america_indiana) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Kentucky america_kentucky) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/North_Dakota america_north_dakota) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Antartica antartica) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Arctic arctic) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Asia asia) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Atlantic atlantic) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Australia australia) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Brazil brazil) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Canada canada) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Chile chile) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Etc etc) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Europe europe) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Indian indian) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Mexico mexico) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/Pacific pacific) -CreateHeader(${NX_TZDB_DIR}/zoneinfo/US us) +CreateHeader(${NX_TZDB_ROMFS_DIR} base) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico) +CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific) +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}) set(FILE_DATA "") foreach(ZONE_FILE ${FILE_LIST}) - if ("${ZONE_FILE}" STREQUAL "\n") + if (ZONE_FILE STREQUAL "\n") continue() endif() @@ -26,13 +26,13 @@ foreach(ZONE_FILE ${FILE_LIST}) foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) math(EXPR BREAK_LINE "(${I} + 2) % 38") - string(SUBSTRING "${ZONE_DATA}" "${I}" "2" HEX_DATA) - if ("${HEX_DATA}" STREQUAL "") + string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA) + if (NOT HEX_DATA) break() endif() string(APPEND FILE_DATA "0x${HEX_DATA},") - if ("${BREAK_LINE}" STREQUAL "0") + if (BREAK_LINE EQUAL 0) string(APPEND FILE_DATA "\n") else() 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 @@ #include "nx_tzdb/america_indiana.h" #include "nx_tzdb/america_kentucky.h" #include "nx_tzdb/america_north_dakota.h" -#include "nx_tzdb/antartica.h" +#include "nx_tzdb/antarctica.h" #include "nx_tzdb/arctic.h" #include "nx_tzdb/asia.h" #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 34df65eff295c2bd9ee9e6a077d662486d5cabb diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 7ae538cf9..bab4f4d0f 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -163,13 +163,14 @@ android { tasks.getByPath("preBuild").dependsOn("ktlintCheck") ktlint { - version.set("0.47.0") + version.set("0.47.1") android.set(true) ignoreFailures.set(false) disabledRules.set( setOf( "no-wildcard-imports", - "package-name" + "package-name", + "import-ordering" ) ) reporters { 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 63b4df273..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,9 @@ enum class BooleanSetting( override val section: String, override val defaultValue: Boolean ) : AbstractBooleanSetting { + CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), + FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), + FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); 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( override val section: String, override val defaultValue: String ) : AbstractStringSetting { + AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); 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 @@ package org.yuzu.yuzu_emu.features.settings.model.view -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting - class HeaderSetting( - setting: AbstractSetting?, - titleId: Int, - descriptionId: Int -) : SettingsItem(setting, titleId, descriptionId) { + titleId: Int +) : SettingsItem(null, titleId, 0) { override val type = TYPE_HEADER } 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 bad34fd88..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 @@ -7,20 +7,20 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting class StringSingleChoiceSetting( - val key: String? = null, setting: AbstractSetting?, titleId: Int, descriptionId: Int, - val choicesId: Array<String>, - private val valuesId: Array<String>?, + val choices: Array<String>, + val values: Array<String>?, + val key: String? = null, private val defaultValue: String? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_STRING_SINGLE_CHOICE fun getValueAt(index: Int): String? { - if (valuesId == null) return null - return if (index >= 0 && index < valuesId.size) { - valuesId[index] + if (values == null) return null + return if (index >= 0 && index < values.size) { + values[index] } else { "" } @@ -36,8 +36,8 @@ class StringSingleChoiceSetting( val selectValueIndex: Int get() { val selectedValue = selectedValue - for (i in valuesId!!.indices) { - if (valuesId[i] == selectedValue) { + for (i in values!!.indices) { + if (values[i] == selectedValue) { return i } } 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 eac6a134b..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 @@ -138,7 +138,7 @@ class SettingsAdapter( clickedItem = item dialog = MaterialAlertDialogBuilder(context) .setTitle(item.nameId) - .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this) + .setSingleChoiceItems(item.choices, item.selectValueIndex, this) .show() } 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 c8c85dd7a..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 @@ -42,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } fun putSetting(setting: AbstractSetting) { - if (setting.section == null) { + if (setting.section == null || setting.key == null) { return } @@ -353,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addAudioSettings(sl: ArrayList<SettingsItem>) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) - sl.add( - SliderSetting( - IntSetting.AUDIO_VOLUME, - R.string.audio_volume, - R.string.audio_volume_description, - 0, - 100, - "%", - IntSetting.AUDIO_VOLUME.key, - IntSetting.AUDIO_VOLUME.defaultValue - ) - ) + sl.apply { + add( + StringSingleChoiceSetting( + StringSetting.AUDIO_OUTPUT_ENGINE, + R.string.audio_output_engine, + 0, + settingsActivity.resources.getStringArray(R.array.outputEngineEntries), + settingsActivity.resources.getStringArray(R.array.outputEngineValues), + StringSetting.AUDIO_OUTPUT_ENGINE.key, + StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue + ) + ) + add( + SliderSetting( + IntSetting.AUDIO_VOLUME, + R.string.audio_volume, + R.string.audio_volume_description, + 0, + 100, + "%", + IntSetting.AUDIO_VOLUME.key, + IntSetting.AUDIO_VOLUME.defaultValue + ) + ) + } } private fun addThemeSettings(sl: ArrayList<SettingsItem>) { @@ -467,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addDebugSettings(sl: ArrayList<SettingsItem>) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) sl.apply { + add(HeaderSetting(R.string.gpu)) add( SingleChoiceSetting( IntSetting.RENDERER_BACKEND, @@ -487,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.RENDERER_DEBUG.defaultValue ) ) + + add(HeaderSetting(R.string.cpu)) + add( + SwitchSetting( + BooleanSetting.CPU_DEBUG_MODE, + R.string.cpu_debug_mode, + R.string.cpu_debug_mode_description, + BooleanSetting.CPU_DEBUG_MODE.key, + BooleanSetting.CPU_DEBUG_MODE.defaultValue + ) + ) + + val fastmem = object : AbstractBooleanSetting { + override var boolean: Boolean + get() = + BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean + set(value) { + BooleanSetting.FASTMEM.boolean = value + BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value + } + override val key: String? = null + override val section: String = Settings.SECTION_CPU + override val isRuntimeEditable: Boolean = false + override val valueAsString: String = "" + override val defaultValue: Any = true + } + add( + SwitchSetting( + fastmem, + R.string.fastmem, + 0 + ) + ) } } } 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 for (i in values.indices) { if (values[i] == item.selectedValue) { binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] + return + } + } + } else if (item is StringSingleChoiceSetting) { + for (i in item.values!!.indices) { + if (item.values[i] == item.selectedValue) { + binding.textSettingDescription.text = item.choices[i] + return } } } else { 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 20a0636df..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 @@ -244,5 +244,21 @@ object SettingsFile { val setting = settings[key] parser.put(header, setting!!.key, setting.valueAsString) } + + BooleanSetting.values().forEach { + if (!keySet.contains(it.key)) { + parser.put(header, it.key, it.valueAsString) + } + } + IntSetting.values().forEach { + if (!keySet.contains(it.key)) { + parser.put(header, it.key, it.valueAsString) + } + } + StringSetting.values().forEach { + if (!keySet.contains(it.key)) { + parser.put(header, it.key, it.valueAsString) + } + } } } 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 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:app="http://schemas.android.com/apk/res-auto" android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" android:minHeight="72dp" + android:paddingVertical="@dimen/spacing_large" android:paddingStart="@dimen/spacing_large" - android:paddingEnd="24dp" - android:paddingVertical="@dimen/spacing_large"> + android:paddingEnd="24dp"> <com.google.android.material.materialswitch.MaterialSwitch android:id="@+id/switch_widget" @@ -19,32 +19,35 @@ android:layout_alignParentEnd="true" android:layout_centerVertical="true" /> - <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.BodySmall" - android:id="@+id/text_setting_description" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_alignStart="@+id/text_setting_name" - android:layout_below="@+id/text_setting_name" - android:layout_marginEnd="@dimen/spacing_large" - android:layout_marginTop="@dimen/spacing_small" - android:layout_toStartOf="@+id/switch_widget" - android:textAlignment="viewStart" - tools:text="@string/frame_limit_enable_description" /> - - <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.HeadlineMedium" - android:id="@+id/text_setting_name" - android:layout_width="0dp" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentStart="true" android:layout_alignParentTop="true" + android:layout_centerVertical="true" android:layout_marginEnd="@dimen/spacing_large" android:layout_toStartOf="@+id/switch_widget" - android:textSize="16sp" - android:textAlignment="viewStart" - app:lineHeight="28dp" - tools:text="@string/frame_limit_enable" /> + android:gravity="center_vertical" + android:orientation="vertical"> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_name" + style="@style/TextAppearance.Material3.HeadlineMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAlignment="viewStart" + android:textSize="16sp" + app:lineHeight="28dp" + tools:text="@string/frame_limit_enable" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_description" + style="@style/TextAppearance.Material3.BodySmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:textAlignment="viewStart" + tools:text="@string/frame_limit_enable_description" /> + + </LinearLayout> </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 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/text_header_name" + style="@style/TextAppearance.Material3.TitleSmall" android:layout_width="match_parent" - android:layout_height="48dp" - android:paddingVertical="4dp" - android:paddingHorizontal="@dimen/spacing_large"> - - <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.TitleSmall" - android:id="@+id/text_header_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start|center_vertical" - android:textColor="?attr/colorPrimary" - android:textAlignment="viewStart" - android:textStyle="bold" - tools:text="CPU Settings" /> - -</FrameLayout> + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:paddingHorizontal="@dimen/spacing_large" + android:paddingVertical="16dp" + android:textAlignment="viewStart" + android:textColor="?attr/colorPrimary" + android:textStyle="bold" + tools:text="CPU Settings" /> diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 7f7b1938c..6d092f7a9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -236,4 +236,15 @@ <item>2</item> </integer-array> + <string-array name="outputEngineEntries"> + <item>@string/auto</item> + <item>@string/cubeb</item> + <item>@string/string_null</item> + </string-array> + <string-array name="outputEngineValues"> + <item>auto</item> + <item>cubeb</item> + <item>null</item> + </string-array> + </resources> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 2f2059d42..cc1d8c39d 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -158,7 +158,6 @@ <string name="set_custom_rtc">Set custom RTC</string> <!-- Graphics settings strings --> - <string name="renderer_api">API</string> <string name="renderer_accuracy">Accuracy level</string> <string name="renderer_resolution">Resolution (Handheld/Docked)</string> <string name="renderer_vsync">VSync mode</string> @@ -172,12 +171,21 @@ <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string> <string name="renderer_reactive_flushing">Use reactive flushing</string> <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> - <string name="renderer_debug">Graphics debugging</string> - <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> <string name="use_disk_shader_cache">Disk shader cache</string> <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> + <!-- Debug settings strings --> + <string name="cpu">CPU</string> + <string name="cpu_debug_mode">CPU Debugging</string> + <string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string> + <string name="gpu">GPU</string> + <string name="renderer_api">API</string> + <string name="renderer_debug">Graphics debugging</string> + <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> + <string name="fastmem">Fastmem</string> + <!-- Audio settings strings --> + <string name="audio_output_engine">Output engine</string> <string name="audio_volume">Volume</string> <string name="audio_volume_description">Specifies the volume of audio output.</string> @@ -196,6 +204,7 @@ <string name="learn_more">Learn more</string> <string name="auto">Auto</string> <string name="submit">Submit</string> + <string name="string_null">Null</string> <!-- GPU driver installation --> <string name="select_gpu_driver">Select GPU driver</string> @@ -366,6 +375,9 @@ <string name="theme_mode_light">Light</string> <string name="theme_mode_dark">Dark</string> + <!-- Audio output engines --> + <string name="cubeb">cubeb</string> + <!-- Black backgrounds theme --> <string name="use_black_backgrounds">Black backgrounds</string> <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> 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 { const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&> tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, {"America", NxTzdb::america}, - {"Antartica", NxTzdb::antartica}, + {"Antarctica", NxTzdb::antarctica}, {"Arctic", NxTzdb::arctic}, {"Asia", NxTzdb::asia}, {"Atlantic", NxTzdb::atlantic}, 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 @@ #include <algorithm> #include <atomic> #include <cinttypes> +#include <condition_variable> +#include <mutex> #include <optional> #include <vector> @@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() { ASSERT(this->IsDummyThread()); // We will block when the scheduler lock is released. - m_dummy_thread_runnable.store(false); + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = false; } void KThread::DummyThreadBeginWait() { @@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() { } // Block until runnable is no longer false. - m_dummy_thread_runnable.wait(false); + std::unique_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); } void KThread::DummyThreadEndWait() { @@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() { ASSERT(this->IsDummyThread()); // Wake up the waiting thread. - m_dummy_thread_runnable.store(true); - m_dummy_thread_runnable.notify_one(); + { + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = true; + } + m_dummy_thread_cv.notify_one(); } 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: std::shared_ptr<Common::Fiber> m_host_context{}; ThreadType m_thread_type{}; StepState m_step_state{}; - std::atomic<bool> m_dummy_thread_runnable{true}; + bool m_dummy_thread_runnable{true}; + std::mutex m_dummy_thread_mutex{}; + std::condition_variable m_dummy_thread_cv{}; // For debugging 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) { // Validate UUID constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` - if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != - ntag_file.uuid.uid[3]) { + if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) != + ntag_file.uuid.crc_check1) { return false; } - if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ - ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { + if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^ + ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) { return false; } @@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& ntag_file) { NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { NTAG215File encoded_data{}; - encoded_data.uid = nfc_data.uuid.uid; - encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; + encoded_data.uid = nfc_data.uuid; + encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2; + encoded_data.internal_number = nfc_data.internal_number; encoded_data.static_lock = nfc_data.static_lock; encoded_data.compability_container = nfc_data.compability_container; encoded_data.hmac_data = nfc_data.user_memory.hmac_data; @@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; encoded_data.application_area = nfc_data.user_memory.application_area; encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; - encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; encoded_data.model_info = nfc_data.user_memory.model_info; encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; encoded_data.dynamic_lock = nfc_data.dynamic_lock; @@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { EncryptedNTAG215File nfc_data{}; - nfc_data.uuid.uid = encoded_data.uid; - nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; - nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; + nfc_data.uuid = encoded_data.uid; + nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2; + nfc_data.internal_number = encoded_data.internal_number; nfc_data.static_lock = encoded_data.static_lock; nfc_data.compability_container = encoded_data.compability_container; nfc_data.user_memory.hmac_data = encoded_data.hmac_data; @@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { return nfc_data; } -u32 GetTagPassword(const TagUuid& uuid) { - // Verify that the generated password is correct - u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]); - password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8; - password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16; - password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24; - return password; -} - HashSeed GetSeed(const NTAG215File& data) { HashSeed seed{ .magic = data.write_counter, .padding = {}, .uid_1 = data.uid, - .nintendo_id_1 = data.nintendo_id, .uid_2 = data.uid, - .nintendo_id_2 = data.nintendo_id, .keygen_salt = data.keygen_salt, }; @@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed output.insert(output.end(), key.magic_bytes.begin(), key.magic_bytes.begin() + key.magic_length); - output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); - output.emplace_back(seed.nintendo_id_1); - output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); - output.emplace_back(seed.nintendo_id_2); + std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{}; + memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid)); + output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); + memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid)); + output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { 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 // Copy the rest of the data directly out_data.uid = in_data.uid; - out_data.nintendo_id = in_data.nintendo_id; - out_data.lock_bytes = in_data.lock_bytes; + out_data.uid_crc_check2 = in_data.uid_crc_check2; + out_data.internal_number = in_data.internal_number; out_data.static_lock = in_data.static_lock; out_data.compability_container = in_data.compability_container; 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>; struct HashSeed { u16_be magic; std::array<u8, 0xE> padding; - NFC::UniqueSerialNumber uid_1; - u8 nintendo_id_1; - NFC::UniqueSerialNumber uid_2; - u8 nintendo_id_2; + TagUuid uid_1; + TagUuid uid_2; std::array<u8, 0x20> keygen_salt; }; static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); @@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); /// Converts from encoded file format to encrypted file format EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); -/// Returns password needed to allow write access to protected memory -u32 GetTagPassword(const TagUuid& uuid); - // Generates Seed needed for key derivation HashSeed GetSeed(const NTAG215File& data); 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 { return ResultWrongDeviceState; } - UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid; - - // Generate random UUID to bypass amiibo load limits - if (Settings::values.random_amiibo_id) { - Common::TinyMT rng{}; - rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); - rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber)); - uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]; - } + UniqueSerialNumber uuid{}; + u8 uuid_length{}; + NfcProtocol protocol{NfcProtocol::TypeA}; + TagType tag_type{TagType::Type2}; if (is_mifare) { - tag_info = { - .uuid = uuid, - .uuid_extension = {}, - .uuid_length = static_cast<u8>(uuid.size()), - .protocol = NfcProtocol::TypeA, - .tag_type = TagType::Type4, + tag_type = TagType::Mifare; + uuid_length = sizeof(NFP::NtagTagUuid); + memcpy(uuid.data(), mifare_data.data(), uuid_length); + } else { + tag_type = TagType::Type2; + uuid_length = sizeof(NFP::NtagTagUuid); + NFP::NtagTagUuid nUuid{ + .part1 = encrypted_tag_data.uuid.part1, + .part2 = encrypted_tag_data.uuid.part2, + .nintendo_id = encrypted_tag_data.uuid.nintendo_id, }; - return ResultSuccess; + memcpy(uuid.data(), &nUuid, uuid_length); + + // Generate random UUID to bypass amiibo load limits + if (Settings::values.random_amiibo_id) { + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + rng.GenerateRandomBytes(uuid.data(), uuid_length); + } } // Protocol and tag type may change here tag_info = { .uuid = uuid, - .uuid_extension = {}, - .uuid_length = static_cast<u8>(uuid.size()), - .protocol = NfcProtocol::TypeA, - .tag_type = TagType::Type2, + .uuid_length = uuid_length, + .protocol = protocol, + .tag_type = tag_type, }; return ResultSuccess; @@ -277,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, std::span<MifareReadBlockData> read_block_data) const { + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + Result result = ResultSuccess; + TagInfo tag_info{}; + result = GetTagInfo(tag_info, true); + + if (result.IsError()) { + return result; + } + + if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { + return ResultInvalidTagType; + } + + if (parameters.size() == 0) { + return ResultInvalidArgument; + } + + const auto unknown = parameters[0].sector_key.unknown; + for (std::size_t i = 0; i < parameters.size(); i++) { + if (unknown != parameters[i].sector_key.unknown) { + return ResultInvalidArgument; + } + } + for (std::size_t i = 0; i < parameters.size(); i++) { result = ReadMifare(parameters[i], read_block_data[i]); if (result.IsError()) { @@ -293,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, MifareReadBlockData& read_block_data) const { const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); read_block_data.sector_number = parameter.sector_number; - - if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == DeviceState::TagRemoved) { - return ResultTagRemoved; - } - return ResultWrongDeviceState; - } - if (mifare_data.size() < sector_index + sizeof(DataBlock)) { - return Mifare::ResultReadError; + return ResultMifareError288; } // TODO: Use parameter.sector_key to read encrypted data @@ -315,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { Result result = ResultSuccess; + TagInfo tag_info{}; + result = GetTagInfo(tag_info, true); + + if (result.IsError()) { + return result; + } + + if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { + return ResultInvalidTagType; + } + + if (parameters.size() == 0) { + return ResultInvalidArgument; + } + + const auto unknown = parameters[0].sector_key.unknown; + for (std::size_t i = 0; i < parameters.size(); i++) { + if (unknown != parameters[i].sector_key.unknown) { + return ResultInvalidArgument; + } + } + for (std::size_t i = 0; i < parameters.size(); i++) { result = WriteMifare(parameters[i]); if (result.IsError()) { @@ -324,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet if (!npad_device->WriteNfc(mifare_data)) { LOG_ERROR(Service_NFP, "Error writing to file"); - return Mifare::ResultReadError; + return ResultMifareError288; } return result; @@ -342,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { } if (mifare_data.size() < sector_index + sizeof(DataBlock)) { - return Mifare::ResultReadError; + return ResultMifareError288; } // TODO: Use parameter.sector_key to encrypt the data @@ -366,7 +414,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { LOG_ERROR(Service_NFP, "Not an amiibo"); - return ResultNotAnAmiibo; + return ResultInvalidTagType; } // The loaded amiibo is not encrypted @@ -381,14 +429,14 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target } if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { - bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess(); + bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; } std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); - WriteBackupData(encrypted_tag_data.uuid.uid, data); + WriteBackupData(encrypted_tag_data.uuid, data); device_state = DeviceState::TagMounted; mount_target = mount_target_; @@ -492,7 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { } memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); - WriteBackupData(encrypted_tag_data.uuid.uid, data); + WriteBackupData(encrypted_tag_data.uuid, data); } if (!npad_device->WriteNfc(data)) { @@ -520,7 +568,7 @@ Result NfcDevice::Restore() { return result; } - result = ReadBackupData(tag_info.uuid, data); + result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); if (result.IsError()) { return result; @@ -548,7 +596,7 @@ Result NfcDevice::Restore() { } if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { - return ResultNotAnAmiibo; + return ResultInvalidTagType; } if (!is_plain_amiibo) { @@ -1194,10 +1242,12 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) { return FlushWithBreak(break_type); } -Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { +Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); constexpr auto backup_dir = "backup"; const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); - const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { return ResultUnableToAccessBackupFile; @@ -1206,10 +1256,19 @@ Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { return ResultSuccess; } -Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const { +Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return HasBackup(uuid, sizeof(NFP::TagUuid)); +} + +Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<u8> data) const { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); constexpr auto backup_dir = "backup"; const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); - const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, Common::FS::FileAccessMode::Read, @@ -1228,12 +1287,21 @@ Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u return ResultSuccess; } -Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) { +Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return ReadBackupData(uuid, sizeof(NFP::TagUuid), data); +} + +Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<const u8> data) { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); constexpr auto backup_dir = "backup"; const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); - const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); - if (HasBackup(uid).IsError()) { + if (HasBackup(uid, uuid_size).IsError()) { if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { return ResultBackupPathAlreadyExist; } @@ -1260,6 +1328,12 @@ Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span< return ResultSuccess; } +Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return WriteBackupData(uuid, sizeof(NFP::TagUuid), data); +} + Result NfcDevice::WriteNtf(std::span<const u8> data) { if (device_state != DeviceState::TagMounted) { 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: Result GetAll(NFP::NfpData& data) const; Result SetAll(const NFP::NfpData& data); Result BreakTag(NFP::BreakType break_type); - Result HasBackup(const NFC::UniqueSerialNumber& uid) const; - Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const; - Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data); + Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const; + Result HasBackup(const NFP::TagUuid& tag_uid) const; + Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<u8> data) const; + Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const; + Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<const u8> data); + Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data); Result WriteNtf(std::span<const u8> data); 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 } if (result.IsSuccess()) { - result = device->ReadBackupData(tag_info.uuid, data); + result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); result = VerifyDeviceResult(device, result); } @@ -569,7 +569,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat } if (result.IsSuccess()) { - result = device->WriteBackupData(tag_info.uuid, data); + result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data); result = VerifyDeviceResult(device, result); } 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); constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); -constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); +constexpr Result ResultNotAMifare(ErrorModule::NFCMifare, 288); } // 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) { void NfcInterface::StartDetection(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop<u64>()}; - const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; - LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); + auto tag_protocol{NfcProtocol::All}; + + if (backend_type == BackendType::Nfc) { + tag_protocol = rp.PopEnum<NfcProtocol>(); + } + LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); auto result = GetManager()->StartDetection(device_handle, tag_protocol); result = TranslateResultToServiceError(result); @@ -355,7 +359,7 @@ Result NfcInterface::TranslateResultToNfp(Result result) const { if (result == ResultApplicationAreaExist) { return NFP::ResultApplicationAreaExist; } - if (result == ResultNotAnAmiibo) { + if (result == ResultInvalidTagType) { return NFP::ResultNotAnAmiibo; } if (result == ResultUnableToAccessBackupFile) { @@ -381,6 +385,9 @@ Result NfcInterface::TranslateResultToMifare(Result result) const { if (result == ResultTagRemoved) { return Mifare::ResultTagRemoved; } + if (result == ResultInvalidTagType) { + return Mifare::ResultNotAMifare; + } LOG_WARNING(Service_NFC, "Result conversion not handled"); return result; } 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); constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); -constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178); +constexpr Result ResultInvalidTagType(ErrorModule::NFC, 178); constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); +constexpr Result ResultMifareError288(ErrorModule::NFC, 288); } // 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 { // This is nn::nfc::TagType enum class TagType : u32 { - None, - Type1, // ISO14443A RW 96-2k bytes 106kbit/s - Type2, // ISO14443A RW/RO 540 bytes 106kbit/s - Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s - Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s - Type5, // ISO15693 RW/RO 540 bytes 106kbit/s + None = 0, + Type1 = 1U << 0, // ISO14443A RW. Topaz + Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN + Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa + Type4A = 1U << 3, // ISO14443A RW/RO. DESFire + Type4B = 1U << 4, // ISO14443B RW/RO. DESFire + Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV + Mifare = 1U << 6, // Mifare classic. Skylanders + All = 0xFFFFFFFF, }; enum class PackedTagType : u8 { - None, - Type1, // ISO14443A RW 96-2k bytes 106kbit/s - Type2, // ISO14443A RW/RO 540 bytes 106kbit/s - Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s - Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s - Type5, // ISO15693 RW/RO 540 bytes 106kbit/s + None = 0, + Type1 = 1U << 0, // ISO14443A RW. Topaz + Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN + Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa + Type4A = 1U << 3, // ISO14443A RW/RO. DESFire + Type4B = 1U << 4, // ISO14443B RW/RO. DESFire + Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV + Mifare = 1U << 6, // Mifare classic. Skylanders + All = 0xFF, }; // This is nn::nfc::NfcProtocol -// Verify this enum. It might be completely wrong default protocol is 0x48 enum class NfcProtocol : u32 { None, TypeA = 1U << 0, // ISO14443A TypeB = 1U << 1, // ISO14443B TypeF = 1U << 2, // Sony FeliCa - Unknown1 = 1U << 3, - Unknown2 = 1U << 5, All = 0xFFFFFFFFU, }; @@ -69,8 +72,7 @@ enum class TestWaveType : u32 { Unknown, }; -using UniqueSerialNumber = std::array<u8, 7>; -using UniqueSerialNumberExtension = std::array<u8, 3>; +using UniqueSerialNumber = std::array<u8, 10>; // This is nn::nfc::DeviceHandle using DeviceHandle = u64; @@ -78,7 +80,6 @@ using DeviceHandle = u64; // This is nn::nfc::TagInfo struct TagInfo { UniqueSerialNumber uuid; - UniqueSerialNumberExtension uuid_extension; u8 uuid_length; INSERT_PADDING_BYTES(0x15); 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 { StartFormatter, }; -using LockBytes = std::array<u8, 2>; +using UuidPart = std::array<u8, 3>; using HashData = std::array<u8, 0x20>; using ApplicationArea = std::array<u8, 0xD8>; using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; @@ -93,12 +93,20 @@ using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; // This is nn::nfp::TagInfo using TagInfo = NFC::TagInfo; +struct NtagTagUuid { + UuidPart part1; + UuidPart part2; + u8 nintendo_id; +}; +static_assert(sizeof(NtagTagUuid) == 7, "NtagTagUuid is an invalid size"); + struct TagUuid { - NFC::UniqueSerialNumber uid; + UuidPart part1; + u8 crc_check1; + UuidPart part2; u8 nintendo_id; - LockBytes lock_bytes; }; -static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); +static_assert(sizeof(TagUuid) == 8, "TagUuid is an invalid size"); struct WriteDate { u16 year; @@ -231,7 +239,8 @@ struct EncryptedAmiiboFile { static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); struct NTAG215File { - LockBytes lock_bytes; // Tag UUID + u8 uid_crc_check2; + u8 internal_number; u16 static_lock; // Set defined pages as read only u32 compability_container; // Defines available memory HashData hmac_data; // Hash @@ -250,8 +259,7 @@ struct NTAG215File { u32_be register_info_crc; ApplicationArea application_area; // Encrypted Game data HashData hmac_tag; // Hash - NFC::UniqueSerialNumber uid; // Unique serial number - u8 nintendo_id; // Tag UUID + TagUuid uid; AmiiboModelInfo model_info; HashData keygen_salt; // Salt u32 dynamic_lock; // Dynamic lock @@ -264,7 +272,9 @@ static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be tr #pragma pack() struct EncryptedNTAG215File { - TagUuid uuid; // Unique serial number + TagUuid uuid; + u8 uuid_crc_check2; + u8 internal_number; u16 static_lock; // Set defined pages as read only u32 compability_container; // Defines available memory 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..63aacd19f 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& static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, CalendarTimeInternal& calendar_time, CalendarAdditionalInfo& calendar_additional_info) { - if ((rules.go_ahead && time < rules.ats[0]) || - (rules.go_back && time > rules.ats[rules.time_count - 1])) { + ASSERT(rules.go_ahead ? rules.time_count > 0 : true); + if ((rules.go_back && time < rules.ats[0]) || + (rules.go_ahead && time > rules.ats[rules.time_count - 1])) { s64 seconds{}; if (time < rules.ats[0]) { seconds = rules.ats[0] - time; 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) { LOG_DEBUG(Service_Time, "called, location_name={}", location_name); TimeZone::TimeZoneRule time_zone_rule{}; - if (const Result result{ - time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; - result != ResultSuccess) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - return; - } + const Result result{time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); ctx.WriteBuffer(time_zone_rule_outbuffer); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) { 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() { texture_cache.SynchronizeComputeDescriptors(); boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; - std::array<GLuint, MAX_TEXTURES> samplers; + boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers; + std::array<GLuint, MAX_TEXTURES> gl_samplers; std::array<GLuint, MAX_TEXTURES> textures; std::array<GLuint, MAX_IMAGES> images; GLsizei sampler_binding{}; @@ -131,7 +132,6 @@ void ComputePipeline::Configure() { for (u32 index = 0; index < desc.count; ++index) { const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - samplers[sampler_binding++] = 0; } } for (const auto& desc : info.image_buffer_descriptors) { @@ -142,8 +142,8 @@ void ComputePipeline::Configure() { const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); - samplers[sampler_binding++] = sampler->Handle(); + VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); + samplers.push_back(sampler); } } for (const auto& desc : info.image_descriptors) { @@ -186,10 +186,17 @@ void ComputePipeline::Configure() { const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + num_image_buffers}; + const VideoCommon::SamplerId* samplers_it{samplers.data()}; texture_binding += num_texture_buffers; image_binding += num_image_buffers; u32 texture_scaling_mask{}; + + for (const auto& desc : info.texture_buffer_descriptors) { + for (u32 index = 0; index < desc.count; ++index) { + gl_samplers[sampler_binding++] = 0; + } + } for (const auto& desc : info.texture_descriptors) { for (u32 index = 0; index < desc.count; ++index) { ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; @@ -198,6 +205,12 @@ void ComputePipeline::Configure() { texture_scaling_mask |= 1u << texture_binding; } ++texture_binding; + + const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; + const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && + !image_view.SupportsAnisotropy()}; + gl_samplers[sampler_binding++] = + use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); } } u32 image_scaling_mask{}; @@ -228,7 +241,7 @@ void ComputePipeline::Configure() { if (texture_binding != 0) { ASSERT(texture_binding == sampler_binding); glBindTextures(0, texture_binding, textures.data()); - glBindSamplers(0, sampler_binding, samplers.data()); + glBindSamplers(0, sampler_binding, gl_samplers.data()); } if (image_binding != 0) { 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 template <typename Spec> void GraphicsPipeline::ConfigureImpl(bool is_indexed) { std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; - std::array<GLuint, MAX_TEXTURES> samplers; + std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers; size_t views_index{}; - GLsizei sampler_binding{}; + size_t samplers_index{}; texture_cache.SynchronizeGraphicsDescriptors(); @@ -337,7 +337,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { for (u32 index = 0; index < desc.count; ++index) { const auto handle{read_handle(desc, index)}; views[views_index++] = {handle.first}; - samplers[sampler_binding++] = 0; } } } @@ -351,8 +350,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { const auto handle{read_handle(desc, index)}; views[views_index++] = {handle.first}; - Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; - samplers[sampler_binding++] = sampler->Handle(); + VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; + samplers[samplers_index++] = sampler; } } if constexpr (Spec::has_images) { @@ -445,10 +444,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { program_manager.BindSourcePrograms(source_programs); } const VideoCommon::ImageViewInOut* views_it{views.data()}; + const VideoCommon::SamplerId* samplers_it{samplers.data()}; GLsizei texture_binding = 0; GLsizei image_binding = 0; + GLsizei sampler_binding{}; std::array<GLuint, MAX_TEXTURES> textures; std::array<GLuint, MAX_IMAGES> images; + std::array<GLuint, MAX_TEXTURES> gl_samplers; const auto prepare_stage{[&](size_t stage) { buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); buffer_cache.BindHostStageBuffers(stage); @@ -465,6 +467,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { u32 stage_image_binding{}; const auto& info{stage_infos[stage]}; + if constexpr (Spec::has_texture_buffers) { + for (const auto& desc : info.texture_buffer_descriptors) { + for (u32 index = 0; index < desc.count; ++index) { + gl_samplers[sampler_binding++] = 0; + } + } + } for (const auto& desc : info.texture_descriptors) { for (u32 index = 0; index < desc.count; ++index) { ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; @@ -474,6 +483,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { } ++texture_binding; ++stage_texture_binding; + + const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; + const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && + !image_view.SupportsAnisotropy()}; + gl_samplers[sampler_binding++] = + use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); } } for (const auto& desc : info.image_descriptors) { @@ -534,7 +549,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { if (texture_binding != 0) { ASSERT(texture_binding == sampler_binding); glBindTextures(0, texture_binding, textures.data()); - glBindSamplers(0, sampler_binding, samplers.data()); + glBindSamplers(0, sampler_binding, gl_samplers.data()); } if (image_binding != 0) { 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 { inst.ReleaseContents(); } - Shader::ObjectPool<Shader::IR::Inst> inst; - Shader::ObjectPool<Shader::IR::Block> block; - Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; + Shader::ObjectPool<Shader::IR::Inst> inst{8192}; + Shader::ObjectPool<Shader::IR::Block> block{32}; + Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; }; 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) { UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); - sampler.Create(); - const GLuint handle = sampler.handle; - glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); - glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); - glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); - glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); - glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); - glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); - glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); - glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); - glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); - glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); - glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); - - if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { - const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); - glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropy); - } else { - LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); - } - if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { - glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); - } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { - LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); - } - if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { - glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); - } else if (seamless == GL_FALSE) { - // We default to false because it's more common - LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); + const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); + + const auto create_sampler = [&](const f32 anisotropy) { + OGLSampler new_sampler; + new_sampler.Create(); + const GLuint handle = new_sampler.handle; + glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); + glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); + glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); + glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); + glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); + glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); + glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); + glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); + glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); + glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); + glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); + + if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { + glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, anisotropy); + } else { + LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); + } + if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { + glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); + } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { + LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); + } + if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { + glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); + } else if (seamless == GL_FALSE) { + // We default to false because it's more common + LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); + } + return new_sampler; + }; + + sampler = create_sampler(max_anisotropy); + + const f32 max_anisotropy_default = static_cast<f32>(1U << config.max_anisotropy); + if (max_anisotropy > max_anisotropy_default) { + sampler_default_anisotropy = create_sampler(max_anisotropy_default); } } 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 { public: explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); - GLuint Handle() const noexcept { + [[nodiscard]] GLuint Handle() const noexcept { return sampler.handle; } + [[nodiscard]] GLuint HandleWithDefaultAnisotropy() const noexcept { + return sampler_default_anisotropy.handle; + } + + [[nodiscard]] bool HasAddedAnisotropy() const noexcept { + return static_cast<bool>(sampler_default_anisotropy.handle); + } + private: OGLSampler sampler; + OGLSampler sampler_default_anisotropy; }; 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: inline void PushImageDescriptors(TextureCache& texture_cache, GuestDescriptorQueue& guest_descriptor_queue, const Shader::Info& info, RescalingPushConstant& rescaling, - const VkSampler*& samplers, + const VideoCommon::SamplerId*& samplers, const VideoCommon::ImageViewInOut*& views) { const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); @@ -187,10 +187,15 @@ inline void PushImageDescriptors(TextureCache& texture_cache, for (const auto& desc : info.texture_descriptors) { for (u32 index = 0; index < desc.count; ++index) { const VideoCommon::ImageViewId image_view_id{(views++)->id}; - const VkSampler sampler{*(samplers++)}; + const VideoCommon::SamplerId sampler_id{*(samplers++)}; ImageView& image_view{texture_cache.GetImageView(image_view_id)}; const VkImageView vk_image_view{image_view.Handle(desc.type)}; - guest_descriptor_queue.AddSampledImage(vk_image_view, sampler); + const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; + const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && + !image_view.SupportsAnisotropy()}; + const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() + : sampler.Handle()}; + guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); rescaling.PushTexture(texture_cache.IsRescaling(image_view)); } } 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, static constexpr size_t max_elements = 64; boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; - boost::container::static_vector<VkSampler, max_elements> samplers; + boost::container::static_vector<VideoCommon::SamplerId, max_elements> samplers; const auto& qmd{kepler_compute.launch_description}; const auto& cbufs{qmd.const_buffer_config}; @@ -160,8 +160,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); - samplers.push_back(sampler->Handle()); + VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); + samplers.push_back(sampler); } } for (const auto& desc : info.image_descriptors) { @@ -192,7 +192,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, buffer_cache.BindHostComputeBuffers(); RescalingPushConstant rescaling; - const VkSampler* samplers_it{samplers.data()}; + const VideoCommon::SamplerId* samplers_it{samplers.data()}; const VideoCommon::ImageViewInOut* views_it{views.data()}; PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, 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) { template <typename Spec> void GraphicsPipeline::ConfigureImpl(bool is_indexed) { std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; - std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers; + std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers; size_t sampler_index{}; size_t view_index{}; @@ -367,8 +367,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { const auto handle{read_handle(desc, index)}; views[view_index++] = {handle.first}; - Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; - samplers[sampler_index++] = sampler->Handle(); + VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; + samplers[sampler_index++] = sampler; } } if constexpr (Spec::has_images) { @@ -453,7 +453,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { RescalingPushConstant rescaling; RenderAreaPushConstant render_area; - const VkSampler* samplers_it{samplers.data()}; + const VideoCommon::SamplerId* samplers_it{samplers.data()}; const VideoCommon::ImageViewInOut* views_it{views.data()}; const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { 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 @@ #include <thread> +#include "common/polyfill_ranges.h" #include "common/settings.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/vulkan_common/vulkan_device.h" 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 { inst.ReleaseContents(); } - Shader::ObjectPool<Shader::IR::Inst> inst; - Shader::ObjectPool<Shader::IR::Block> block; - Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; + Shader::ObjectPool<Shader::IR::Inst> inst{8192}; + Shader::ObjectPool<Shader::IR::Block> block{32}; + Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; }; 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 // Some games have samplers with garbage. Sanitize them here. const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); - sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = pnext, - .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), - .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), - .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), - .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), - .mipLodBias = tsc.LodBias(), - .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = max_anisotropy, - .compareEnable = tsc.depth_compare_enabled, - .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), - .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), - .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), - .borderColor = - arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), - .unnormalizedCoordinates = VK_FALSE, - }); + const auto create_sampler = [&](const f32 anisotropy) { + return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = pnext, + .flags = 0, + .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), + .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), + .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), + .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), + .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), + .mipLodBias = tsc.LodBias(), + .anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), + .maxAnisotropy = anisotropy, + .compareEnable = tsc.depth_compare_enabled, + .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), + .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), + .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), + .borderColor = + arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), + .unnormalizedCoordinates = VK_FALSE, + }); + }; + + sampler = create_sampler(max_anisotropy); + + const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy); + if (max_anisotropy > max_anisotropy_default) { + sampler_default_anisotropy = create_sampler(max_anisotropy_default); + } } 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: return *sampler; } + [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { + return *sampler_default_anisotropy; + } + + [[nodiscard]] bool HasAddedAnisotropy() const noexcept { + return static_cast<bool>(sampler_default_anisotropy); + } + private: vk::Sampler sampler; + vk::Sampler sampler_default_anisotropy; }; 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 ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} +bool ImageViewBase::SupportsAnisotropy() const noexcept { + const bool has_mips = range.extent.levels > 1; + const bool is_2d = type == ImageViewType::e2D || type == ImageViewType::e2DArray; + if (!has_mips || !is_2d) { + return false; + } + + switch (format) { + case PixelFormat::R8_UNORM: + case PixelFormat::R8_SNORM: + case PixelFormat::R8_SINT: + case PixelFormat::R8_UINT: + case PixelFormat::BC4_UNORM: + case PixelFormat::BC4_SNORM: + case PixelFormat::BC5_UNORM: + case PixelFormat::BC5_SNORM: + case PixelFormat::R32G32_FLOAT: + case PixelFormat::R32G32_SINT: + case PixelFormat::R32_FLOAT: + case PixelFormat::R16_FLOAT: + case PixelFormat::R16_UNORM: + case PixelFormat::R16_SNORM: + case PixelFormat::R16_UINT: + case PixelFormat::R16_SINT: + case PixelFormat::R16G16_UNORM: + case PixelFormat::R16G16_FLOAT: + case PixelFormat::R16G16_UINT: + case PixelFormat::R16G16_SINT: + case PixelFormat::R16G16_SNORM: + case PixelFormat::R8G8_UNORM: + case PixelFormat::R8G8_SNORM: + case PixelFormat::R8G8_SINT: + case PixelFormat::R8G8_UINT: + case PixelFormat::R32G32_UINT: + case PixelFormat::R32_UINT: + case PixelFormat::R32_SINT: + case PixelFormat::G4R4_UNORM: + // Depth formats + case PixelFormat::D32_FLOAT: + case PixelFormat::D16_UNORM: + // Stencil formats + case PixelFormat::S8_UINT: + // DepthStencil formats + case PixelFormat::D24_UNORM_S8_UINT: + case PixelFormat::S8_UINT_D24_UNORM: + case PixelFormat::D32_FLOAT_S8_UINT: + return false; + default: + return true; + } +} + } // 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 { return type == ImageViewType::Buffer; } + [[nodiscard]] bool SupportsAnisotropy() const noexcept; + ImageId image_id{}; GPUVAddr gpu_addr = 0; PixelFormat format{}; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c7f7448e9..4027d860b 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -222,30 +222,50 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) { template <class P> typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { + return &slot_samplers[GetGraphicsSamplerId(index)]; +} + +template <class P> +typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { + return &slot_samplers[GetComputeSamplerId(index)]; +} + +template <class P> +SamplerId TextureCache<P>::GetGraphicsSamplerId(u32 index) { if (index > channel_state->graphics_sampler_table.Limit()) { LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); - return &slot_samplers[NULL_SAMPLER_ID]; + return NULL_SAMPLER_ID; } const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); SamplerId& id = channel_state->graphics_sampler_ids[index]; if (is_new) { id = FindSampler(descriptor); } - return &slot_samplers[id]; + return id; } template <class P> -typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { +SamplerId TextureCache<P>::GetComputeSamplerId(u32 index) { if (index > channel_state->compute_sampler_table.Limit()) { LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); - return &slot_samplers[NULL_SAMPLER_ID]; + return NULL_SAMPLER_ID; } const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); SamplerId& id = channel_state->compute_sampler_ids[index]; if (is_new) { id = FindSampler(descriptor); } - return &slot_samplers[id]; + return id; +} + +template <class P> +const typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) const noexcept { + return slot_samplers[id]; +} + +template <class P> +typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept { + return slot_samplers[id]; } template <class P> diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 3bfa92154..d96ddea9d 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: /// Get the sampler from the compute descriptor table in the specified index Sampler* GetComputeSampler(u32 index); + /// Get the sampler id from the graphics descriptor table in the specified index + SamplerId GetGraphicsSamplerId(u32 index); + + /// Get the sampler id from the compute descriptor table in the specified index + SamplerId GetComputeSamplerId(u32 index); + + /// Return a constant reference to the given sampler id + [[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept; + + /// Return a reference to the given sampler id + [[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept; + /// Refresh the state for graphics image view and sampler descriptors void SynchronizeGraphicsDescriptors(); 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 { } float TSCEntry::MaxAnisotropy() const noexcept { - if (max_anisotropy == 0 && mipmap_filter != TextureMipmapFilter::Linear) { + const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None; + const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256; + const bool is_bilinear_filter = min_filter == TextureFilter::Linear && + reduction_filter == SamplerReduction::WeightedAverage; + if (max_anisotropy == 0 && (!is_suitable_mipmap_filter || !has_regular_lods || + !is_bilinear_filter || depth_compare_enabled)) { return 1.0f; } const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); |