summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/.gitignore3
-rw-r--r--src/android/app/build.gradle.kts21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt49
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt66
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt213
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt99
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt138
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt266
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt67
-rw-r--r--src/android/app/src/main/jni/config.cpp2
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp14
-rw-r--r--src/android/app/src/main/jni/native.cpp36
-rw-r--r--src/android/app/src/main/res/drawable/ic_export.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_import.xml9
-rw-r--r--src/android/app/src/main/res/layout/activity_settings.xml2
-rw-r--r--src/android/app/src/main/res/layout/card_installable.xml71
-rw-r--r--src/android/app/src/main/res/layout/dialog_progress_bar.xml28
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml5
-rw-r--r--src/android/app/src/main/res/layout/fragment_installables.xml31
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-w600dp/integers.xml6
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml1
-rw-r--r--src/android/app/src/main/res/values/integers.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml24
-rw-r--r--src/audio_core/CMakeLists.txt16
-rw-r--r--src/audio_core/adsp/adsp.cpp13
-rw-r--r--src/audio_core/adsp/adsp.h3
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp48
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h45
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp107
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h38
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp269
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h92
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp111
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h39
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h17
-rw-r--r--src/audio_core/adsp/mailbox.h27
-rw-r--r--src/audio_core/opus/decoder.cpp179
-rw-r--r--src/audio_core/opus/decoder.h53
-rw-r--r--src/audio_core/opus/decoder_manager.cpp102
-rw-r--r--src/audio_core/opus/decoder_manager.h38
-rw-r--r--src/audio_core/opus/hardware_opus.cpp241
-rw-r--r--src/audio_core/opus/hardware_opus.h45
-rw-r--r--src/audio_core/opus/parameters.h54
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp4
-rw-r--r--src/audio_core/renderer/system.cpp10
-rw-r--r--src/common/CMakeLists.txt5
-rw-r--r--src/common/bounded_threadsafe_queue.h4
-rw-r--r--src/common/fs/fs.cpp15
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h6
-rw-r--r--src/common/settings_common.h10
-rw-r--r--src/common/settings_setting.h33
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/core.cpp18
-rw-r--r--src/core/core.h6
-rw-r--r--src/core/crypto/key_manager.cpp8
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp4
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp4
-rw-r--r--src/core/hid/emulated_controller.cpp11
-rw-r--r--src/core/hid/emulated_controller.h2
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hle/kernel/k_hardware_timer.cpp4
-rw-r--r--src/core/hle/kernel/k_process.cpp5
-rw-r--r--src/core/hle/kernel/k_process.h8
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/am/am.cpp230
-rw-r--r--src/core/hle/service/am/am.h34
-rw-r--r--src/core/hle/service/am/applet_ae.cpp48
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp52
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h7
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp20
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp722
-rw-r--r--src/core/hle/service/audio/hwopus.h25
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp65
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp67
-rw-r--r--src/core/hle/service/hid/controllers/npad.h22
-rw-r--r--src/core/hle/service/hid/hid.cpp33
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/mii/mii.cpp431
-rw-r--r--src/core/hle/service/mii/mii.h18
-rw-r--r--src/core/hle/service/mii/mii_database.cpp142
-rw-r--r--src/core/hle/service/mii/mii_database.h66
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp410
-rw-r--r--src/core/hle/service/mii/mii_manager.h82
-rw-r--r--src/core/hle/service/mii/mii_result.h9
-rw-r--r--src/core/hle/service/mii/mii_types.h18
-rw-r--r--src/core/hle/service/mii/mii_util.h26
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp6
-rw-r--r--src/core/hle/service/mii/types/char_info.h106
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp218
-rw-r--r--src/core/hle/service/mii/types/core_data.h7
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp12
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp83
-rw-r--r--src/core/hle/service/mii/types/store_data.h19
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp78
-rw-r--r--src/core/hle/service/nfc/common/device.cpp20
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp17
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp8
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/nro.cpp3
-rw-r--r--src/core/loader/nso.cpp5
-rw-r--r--src/core/loader/nsp.cpp3
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp15
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp7
-rw-r--r--src/video_core/CMakeLists.txt6
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h18
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h12
-rw-r--r--src/video_core/control/channel_state_cache.h2
-rw-r--r--src/video_core/engines/draw_manager.h1
-rw-r--r--src/video_core/engines/maxwell_3d.cpp74
-rw-r--r--src/video_core/engines/maxwell_3d.h3
-rw-r--r--src/video_core/engines/maxwell_dma.cpp17
-rw-r--r--src/video_core/engines/maxwell_dma.h55
-rw-r--r--src/video_core/engines/puller.cpp13
-rw-r--r--src/video_core/fence_manager.h21
-rw-r--r--src/video_core/gpu.cpp4
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt6
-rw-r--r--src/video_core/host_shaders/convert_msaa_to_non_msaa.comp7
-rw-r--r--src/video_core/host_shaders/convert_non_msaa_to_msaa.comp7
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum.comp173
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp138
-rw-r--r--src/video_core/host_shaders/resolve_conditional_render.comp20
-rw-r--r--src/video_core/macro/macro_hle.cpp49
-rw-r--r--src/video_core/query_cache.h13
-rw-r--r--src/video_core/query_cache/bank_base.h104
-rw-r--r--src/video_core/query_cache/query_base.h70
-rw-r--r--src/video_core/query_cache/query_cache.h580
-rw-r--r--src/video_core/query_cache/query_cache_base.h181
-rw-r--r--src/video_core/query_cache/query_stream.h149
-rw-r--r--src/video_core/query_cache/types.h74
-rw-r--r--src/video_core/rasterizer_interface.h13
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp18
-rw-r--r--src/video_core/renderer_null/null_rasterizer.h7
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp40
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h7
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp320
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h50
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp1595
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h106
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp135
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h12
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp127
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h7
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp2
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp23
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h85
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h40
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_ui.cpp7
-rw-r--r--src/yuzu/configuration/shared_widget.cpp171
-rw-r--r--src/yuzu/configuration/shared_widget.h21
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp59
-rw-r--r--src/yuzu/main.h4
-rw-r--r--src/yuzu/main.ui9
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp5
207 files changed, 9776 insertions, 1854 deletions
diff --git a/src/android/.gitignore b/src/android/.gitignore
index 121cc8484..ff7121acd 100644
--- a/src/android/.gitignore
+++ b/src/android/.gitignore
@@ -63,3 +63,6 @@ fastlane/Preview.html
63fastlane/screenshots 63fastlane/screenshots
64fastlane/test_output 64fastlane/test_output
65fastlane/readme.md 65fastlane/readme.md
66
67# Autogenerated library for vulkan validation layers
68libVkLayer_khronos_validation.so
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index fe79a701c..84a3308b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -77,13 +77,30 @@ android {
77 buildConfigField("String", "BRANCH", "\"${getBranch()}\"") 77 buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
78 } 78 }
79 79
80 val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
81 if (keystoreFile != null) {
82 signingConfigs {
83 create("release") {
84 storeFile = file(keystoreFile)
85 storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
86 keyAlias = System.getenv("ANDROID_KEY_ALIAS")
87 keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
88 }
89 }
90 }
91
80 // Define build types, which are orthogonal to product flavors. 92 // Define build types, which are orthogonal to product flavors.
81 buildTypes { 93 buildTypes {
82 94
83 // Signed by release key, allowing for upload to Play Store. 95 // Signed by release key, allowing for upload to Play Store.
84 release { 96 release {
97 signingConfig = if (keystoreFile != null) {
98 signingConfigs.getByName("release")
99 } else {
100 signingConfigs.getByName("debug")
101 }
102
85 resValue("string", "app_name_suffixed", "yuzu") 103 resValue("string", "app_name_suffixed", "yuzu")
86 signingConfig = signingConfigs.getByName("debug")
87 isMinifyEnabled = true 104 isMinifyEnabled = true
88 isDebuggable = false 105 isDebuggable = false
89 proguardFiles( 106 proguardFiles(
@@ -197,7 +214,7 @@ dependencies {
197 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
198 implementation("io.coil-kt:coil:2.2.2") 215 implementation("io.coil-kt:coil:2.2.2")
199 implementation("androidx.core:core-splashscreen:1.0.1") 216 implementation("androidx.core:core-splashscreen:1.0.1")
200 implementation("androidx.window:window:1.1.0") 217 implementation("androidx.window:window:1.2.0-beta03")
201 implementation("org.ini4j:ini4j:0.5.4") 218 implementation("org.ini4j:ini4j:0.5.4")
202 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 219 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
203 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
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 21f67f32a..6e39e542b 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
@@ -247,7 +247,12 @@ object NativeLibrary {
247 247
248 external fun setAppDirectory(directory: String) 248 external fun setAppDirectory(directory: String)
249 249
250 external fun installFileToNand(filename: String): Int 250 /**
251 * Installs a nsp or xci file to nand
252 * @param filename String representation of file uri
253 * @param extension Lowercase string representation of file extension without "."
254 */
255 external fun installFileToNand(filename: String, extension: String): Int
251 256
252 external fun initializeGpuDriver( 257 external fun initializeGpuDriver(
253 hookLibDir: String?, 258 hookLibDir: String?,
@@ -512,6 +517,11 @@ object NativeLibrary {
512 external fun submitInlineKeyboardInput(key_code: Int) 517 external fun submitInlineKeyboardInput(key_code: Int)
513 518
514 /** 519 /**
520 * Creates a generic user directory if it doesn't exist already
521 */
522 external fun initializeEmptyUserDirectory()
523
524 /**
515 * Button type for use in onTouchEvent 525 * Button type for use in onTouchEvent
516 */ 526 */
517 object ButtonType { 527 object ButtonType {
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 d4ae39661..e96a2059b 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
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.activities 4package org.yuzu.yuzu_emu.activities
5 5
6import android.annotation.SuppressLint
6import android.app.Activity 7import android.app.Activity
7import android.app.PendingIntent 8import android.app.PendingIntent
8import android.app.PictureInPictureParams 9import android.app.PictureInPictureParams
@@ -397,6 +398,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
397 } 398 }
398 } 399 }
399 400
401 @SuppressLint("UnspecifiedRegisterReceiverFlag")
400 override fun onPictureInPictureModeChanged( 402 override fun onPictureInPictureModeChanged(
401 isInPictureInPictureMode: Boolean, 403 isInPictureInPictureMode: Boolean,
402 newConfig: Configuration 404 newConfig: Configuration
@@ -409,7 +411,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
409 addAction(actionMute) 411 addAction(actionMute)
410 addAction(actionUnmute) 412 addAction(actionUnmute)
411 }.also { 413 }.also {
412 registerReceiver(pictureInPictureReceiver, it) 414 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
415 registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED)
416 } else {
417 registerReceiver(pictureInPictureReceiver, it)
418 }
413 } 419 }
414 } else { 420 } else {
415 try { 421 try {
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 1675627a1..58ce343f4 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
@@ -49,6 +49,7 @@ class HomeSettingAdapter(
49 holder.option.onClick.invoke() 49 holder.option.onClick.invoke()
50 } else { 50 } else {
51 MessageDialogFragment.newInstance( 51 MessageDialogFragment.newInstance(
52 activity,
52 titleId = holder.option.disabledTitleId, 53 titleId = holder.option.disabledTitleId,
53 descriptionId = holder.option.disabledMessageId 54 descriptionId = holder.option.disabledMessageId
54 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) 55 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
new file mode 100644
index 000000000..e960fbaab
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -0,0 +1,49 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.view.LayoutInflater
7import android.view.View
8import android.view.ViewGroup
9import androidx.recyclerview.widget.RecyclerView
10import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
11import org.yuzu.yuzu_emu.model.Installable
12
13class InstallableAdapter(private val installables: List<Installable>) :
14 RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
15 override fun onCreateViewHolder(
16 parent: ViewGroup,
17 viewType: Int
18 ): InstallableAdapter.InstallableViewHolder {
19 val binding =
20 CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
21 return InstallableViewHolder(binding)
22 }
23
24 override fun getItemCount(): Int = installables.size
25
26 override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
27 holder.bind(installables[position])
28
29 inner class InstallableViewHolder(val binding: CardInstallableBinding) :
30 RecyclerView.ViewHolder(binding.root) {
31 lateinit var installable: Installable
32
33 fun bind(installable: Installable) {
34 this.installable = installable
35
36 binding.title.setText(installable.titleId)
37 binding.description.setText(installable.descriptionId)
38
39 if (installable.install != null) {
40 binding.buttonInstall.visibility = View.VISIBLE
41 binding.buttonInstall.setOnClickListener { installable.install.invoke() }
42 }
43 if (installable.export != null) {
44 binding.buttonExport.visibility = View.VISIBLE
45 binding.buttonExport.setOnClickListener { installable.export.invoke() }
46 }
47 }
48 }
49}
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 4d2f2f604..c73edd50e 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
@@ -21,6 +21,7 @@ import androidx.navigation.navArgs
21import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest 22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch 23import kotlinx.coroutines.launch
24import org.yuzu.yuzu_emu.NativeLibrary
24import java.io.IOException 25import java.io.IOException
25import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 27import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -168,7 +169,7 @@ class SettingsActivity : AppCompatActivity() {
168 if (!settingsFile.delete()) { 169 if (!settingsFile.delete()) {
169 throw IOException("Failed to delete $settingsFile") 170 throw IOException("Failed to delete $settingsFile")
170 } 171 }
171 Settings.settingsList.forEach { it.reset() } 172 NativeLibrary.reloadSettings()
172 173
173 Toast.makeText( 174 Toast.makeText(
174 applicationContext, 175 applicationContext,
@@ -181,12 +182,14 @@ class SettingsActivity : AppCompatActivity() {
181 private fun setInsets() { 182 private fun setInsets() {
182 ViewCompat.setOnApplyWindowInsetsListener( 183 ViewCompat.setOnApplyWindowInsetsListener(
183 binding.navigationBarShade 184 binding.navigationBarShade
184 ) { view: View, windowInsets: WindowInsetsCompat -> 185 ) { _: View, windowInsets: WindowInsetsCompat ->
185 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 186 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
186 187
187 val mlpShade = view.layoutParams as MarginLayoutParams 188 // The only situation where we care to have a nav bar shade is when it's at the bottom
188 mlpShade.height = barInsets.bottom 189 // of the screen where scrolling list elements can go behind it.
189 view.layoutParams = mlpShade 190 val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
191 mlpNavShade.height = barInsets.bottom
192 binding.navigationBarShade.layoutParams = mlpNavShade
190 193
191 windowInsets 194 windowInsets
192 } 195 }
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 3e6c157c7..e6ad2aa77 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
@@ -15,9 +15,9 @@ import android.net.Uri
15import android.os.Bundle 15import android.os.Bundle
16import android.os.Handler 16import android.os.Handler
17import android.os.Looper 17import android.os.Looper
18import android.util.Rational
19import android.view.* 18import android.view.*
20import android.widget.TextView 19import android.widget.TextView
20import android.widget.Toast
21import androidx.activity.OnBackPressedCallback 21import androidx.activity.OnBackPressedCallback
22import androidx.appcompat.widget.PopupMenu 22import androidx.appcompat.widget.PopupMenu
23import androidx.core.content.res.ResourcesCompat 23import androidx.core.content.res.ResourcesCompat
@@ -54,6 +54,7 @@ import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 54import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
56import org.yuzu.yuzu_emu.utils.* 56import org.yuzu.yuzu_emu.utils.*
57import java.lang.NullPointerException
57 58
58class EmulationFragment : Fragment(), SurfaceHolder.Callback { 59class EmulationFragment : Fragment(), SurfaceHolder.Callback {
59 private lateinit var preferences: SharedPreferences 60 private lateinit var preferences: SharedPreferences
@@ -105,10 +106,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
105 null 106 null
106 } 107 }
107 } 108 }
108 game = if (args.game != null) { 109
109 args.game!! 110 try {
110 } else { 111 game = if (args.game != null) {
111 intentGame ?: error("[EmulationFragment] No bootable game present!") 112 args.game!!
113 } else {
114 intentGame!!
115 }
116 } catch (e: NullPointerException) {
117 Toast.makeText(
118 requireContext(),
119 R.string.no_game_present,
120 Toast.LENGTH_SHORT
121 ).show()
122 requireActivity().finish()
123 return
112 } 124 }
113 125
114 // So this fragment doesn't restart on configuration changes; i.e. rotation. 126 // So this fragment doesn't restart on configuration changes; i.e. rotation.
@@ -132,6 +144,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
132 // This is using the correct scope, lint is just acting up 144 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector") 145 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 146 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
147 super.onViewCreated(view, savedInstanceState)
148 if (requireActivity().isFinishing) {
149 return
150 }
151
135 binding.surfaceEmulation.holder.addCallback(this) 152 binding.surfaceEmulation.holder.addCallback(this)
136 binding.showFpsText.setTextColor(Color.YELLOW) 153 binding.showFpsText.setTextColor(Color.YELLOW)
137 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } 154 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
@@ -287,24 +304,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
287 304
288 override fun onConfigurationChanged(newConfig: Configuration) { 305 override fun onConfigurationChanged(newConfig: Configuration) {
289 super.onConfigurationChanged(newConfig) 306 super.onConfigurationChanged(newConfig)
307 if (_binding == null) {
308 return
309 }
310
311 updateScreenLayout()
290 if (emulationActivity?.isInPictureInPictureMode == true) { 312 if (emulationActivity?.isInPictureInPictureMode == true) {
291 if (binding.drawerLayout.isOpen) { 313 if (binding.drawerLayout.isOpen) {
292 binding.drawerLayout.close() 314 binding.drawerLayout.close()
293 } 315 }
294 if (EmulationMenuSettings.showOverlay) { 316 if (EmulationMenuSettings.showOverlay) {
295 binding.surfaceInputOverlay.post { 317 binding.surfaceInputOverlay.visibility = View.INVISIBLE
296 binding.surfaceInputOverlay.visibility = View.VISIBLE
297 }
298 } 318 }
299 } else { 319 } else {
300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { 320 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
301 binding.surfaceInputOverlay.post { 321 binding.surfaceInputOverlay.visibility = View.VISIBLE
302 binding.surfaceInputOverlay.visibility = View.VISIBLE
303 }
304 } else { 322 } else {
305 binding.surfaceInputOverlay.post { 323 binding.surfaceInputOverlay.visibility = View.INVISIBLE
306 binding.surfaceInputOverlay.visibility = View.INVISIBLE
307 }
308 } 324 }
309 if (!isInFoldableLayout) { 325 if (!isInFoldableLayout) {
310 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 326 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -328,7 +344,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
328 } 344 }
329 345
330 override fun onPause() { 346 override fun onPause() {
331 if (emulationState.isRunning) { 347 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
332 emulationState.pause() 348 emulationState.pause()
333 } 349 }
334 super.onPause() 350 super.onPause()
@@ -394,16 +410,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
394 } 410 }
395 411
396 private fun updateScreenLayout() { 412 private fun updateScreenLayout() {
397 binding.surfaceEmulation.setAspectRatio( 413 binding.surfaceEmulation.setAspectRatio(null)
398 when (IntSetting.RENDERER_ASPECT_RATIO.int) {
399 0 -> Rational(16, 9)
400 1 -> Rational(4, 3)
401 2 -> Rational(21, 9)
402 3 -> Rational(16, 10)
403 4 -> null // Stretch
404 else -> Rational(16, 9)
405 }
406 )
407 emulationActivity?.buildPictureInPictureParams() 414 emulationActivity?.buildPictureInPictureParams()
408 updateOrientation() 415 updateOrientation()
409 } 416 }
@@ -693,7 +700,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
693 private class EmulationState(private val gamePath: String) { 700 private class EmulationState(private val gamePath: String) {
694 private var state: State 701 private var state: State
695 private var surface: Surface? = null 702 private var surface: Surface? = null
696 private var runWhenSurfaceIsValid = false
697 703
698 init { 704 init {
699 // Starting state is stopped. 705 // Starting state is stopped.
@@ -751,8 +757,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
751 // If the surface is set, run now. Otherwise, wait for it to get set. 757 // If the surface is set, run now. Otherwise, wait for it to get set.
752 if (surface != null) { 758 if (surface != null) {
753 runWithValidSurface() 759 runWithValidSurface()
754 } else {
755 runWhenSurfaceIsValid = true
756 } 760 }
757 } 761 }
758 762
@@ -760,7 +764,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
760 @Synchronized 764 @Synchronized
761 fun newSurface(surface: Surface?) { 765 fun newSurface(surface: Surface?) {
762 this.surface = surface 766 this.surface = surface
763 if (runWhenSurfaceIsValid) { 767 if (this.surface != null) {
764 runWithValidSurface() 768 runWithValidSurface()
765 } 769 }
766 } 770 }
@@ -788,10 +792,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
788 } 792 }
789 793
790 private fun runWithValidSurface() { 794 private fun runWithValidSurface() {
791 runWhenSurfaceIsValid = false 795 NativeLibrary.surfaceChanged(surface)
792 when (state) { 796 when (state) {
793 State.STOPPED -> { 797 State.STOPPED -> {
794 NativeLibrary.surfaceChanged(surface)
795 val emulationThread = Thread({ 798 val emulationThread = Thread({
796 Log.debug("[EmulationFragment] Starting emulation thread.") 799 Log.debug("[EmulationFragment] Starting emulation thread.")
797 NativeLibrary.run(gamePath) 800 NativeLibrary.run(gamePath)
@@ -801,7 +804,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
801 804
802 State.PAUSED -> { 805 State.PAUSED -> {
803 Log.debug("[EmulationFragment] Resuming emulation.") 806 Log.debug("[EmulationFragment] Resuming emulation.")
804 NativeLibrary.surfaceChanged(surface)
805 NativeLibrary.unpauseEmulation() 807 NativeLibrary.unpauseEmulation()
806 } 808 }
807 809
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 c119e69c9..8923c0ea2 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
@@ -118,18 +118,13 @@ class HomeSettingsFragment : Fragment() {
118 ) 118 )
119 add( 119 add(
120 HomeSetting( 120 HomeSetting(
121 R.string.install_amiibo_keys, 121 R.string.manage_yuzu_data,
122 R.string.install_amiibo_keys_description, 122 R.string.manage_yuzu_data_description,
123 R.drawable.ic_nfc, 123 R.drawable.ic_install,
124 { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) } 124 {
125 ) 125 binding.root.findNavController()
126 ) 126 .navigate(R.id.action_homeSettingsFragment_to_installableFragment)
127 add( 127 }
128 HomeSetting(
129 R.string.install_game_content,
130 R.string.install_game_content_description,
131 R.drawable.ic_system_update_alt,
132 { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
133 ) 128 )
134 ) 129 )
135 add( 130 add(
@@ -150,35 +145,6 @@ class HomeSettingsFragment : Fragment() {
150 ) 145 )
151 add( 146 add(
152 HomeSetting( 147 HomeSetting(
153 R.string.manage_save_data,
154 R.string.import_export_saves_description,
155 R.drawable.ic_save,
156 {
157 ImportExportSavesFragment().show(
158 parentFragmentManager,
159 ImportExportSavesFragment.TAG
160 )
161 }
162 )
163 )
164 add(
165 HomeSetting(
166 R.string.install_prod_keys,
167 R.string.install_prod_keys_description,
168 R.drawable.ic_unlock,
169 { mainActivity.getProdKey.launch(arrayOf("*/*")) }
170 )
171 )
172 add(
173 HomeSetting(
174 R.string.install_firmware,
175 R.string.install_firmware_description,
176 R.drawable.ic_firmware,
177 { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
178 )
179 )
180 add(
181 HomeSetting(
182 R.string.share_log, 148 R.string.share_log,
183 R.string.share_log_description, 149 R.string.share_log_description,
184 R.drawable.ic_log, 150 R.drawable.ic_log,
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
deleted file mode 100644
index f38aeea53..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ /dev/null
@@ -1,213 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import android.provider.DocumentsContract
11import android.widget.Toast
12import androidx.activity.result.ActivityResultLauncher
13import androidx.activity.result.contract.ActivityResultContracts
14import androidx.appcompat.app.AppCompatActivity
15import androidx.documentfile.provider.DocumentFile
16import androidx.fragment.app.DialogFragment
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import java.io.BufferedOutputStream
19import java.io.File
20import java.io.FileOutputStream
21import java.io.FilenameFilter
22import java.time.LocalDateTime
23import java.time.format.DateTimeFormatter
24import java.util.zip.ZipEntry
25import java.util.zip.ZipOutputStream
26import kotlinx.coroutines.CoroutineScope
27import kotlinx.coroutines.Dispatchers
28import kotlinx.coroutines.launch
29import kotlinx.coroutines.withContext
30import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication
32import org.yuzu.yuzu_emu.features.DocumentProvider
33import org.yuzu.yuzu_emu.getPublicFilesDir
34import org.yuzu.yuzu_emu.utils.FileUtil
35
36class ImportExportSavesFragment : DialogFragment() {
37 private val context = YuzuApplication.appContext
38 private val savesFolder =
39 "${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
40
41 // Get first subfolder in saves folder (should be the user folder)
42 private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
43 private var lastZipCreated: File? = null
44
45 private lateinit var startForResultExportSave: ActivityResultLauncher<Intent>
46 private lateinit var documentPicker: ActivityResultLauncher<Array<String>>
47
48 override fun onCreate(savedInstanceState: Bundle?) {
49 super.onCreate(savedInstanceState)
50 val activity = requireActivity() as AppCompatActivity
51
52 val activityResultRegistry = requireActivity().activityResultRegistry
53 startForResultExportSave = activityResultRegistry.register(
54 "startForResultExportSaveKey",
55 ActivityResultContracts.StartActivityForResult()
56 ) {
57 File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
58 }
59 documentPicker = activityResultRegistry.register(
60 "documentPickerKey",
61 ActivityResultContracts.OpenDocument()
62 ) {
63 it?.let { uri -> importSave(uri, activity) }
64 }
65 }
66
67 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
68 return if (savesFolderRoot == "") {
69 MaterialAlertDialogBuilder(requireContext())
70 .setTitle(R.string.manage_save_data)
71 .setMessage(R.string.import_export_saves_no_profile)
72 .setPositiveButton(android.R.string.ok, null)
73 .show()
74 } else {
75 MaterialAlertDialogBuilder(requireContext())
76 .setTitle(R.string.manage_save_data)
77 .setMessage(R.string.manage_save_data_description)
78 .setNegativeButton(R.string.export_saves) { _, _ ->
79 exportSave()
80 }
81 .setPositiveButton(R.string.import_saves) { _, _ ->
82 documentPicker.launch(arrayOf("application/zip"))
83 }
84 .setNeutralButton(android.R.string.cancel, null)
85 .show()
86 }
87 }
88
89 /**
90 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
91 * @return true if the zip file is successfully created, false otherwise.
92 */
93 private fun zipSave(): Boolean {
94 try {
95 val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp")
96 tempFolder.mkdirs()
97 val saveFolder = File(savesFolderRoot)
98 val outputZipFile = File(
99 tempFolder,
100 "yuzu saves - ${
101 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
102 }.zip"
103 )
104 outputZipFile.createNewFile()
105 ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
106 saveFolder.walkTopDown().forEach { file ->
107 val zipFileName =
108 file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
109 if (zipFileName == "") {
110 return@forEach
111 }
112 val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
113 zos.putNextEntry(entry)
114 if (file.isFile) {
115 file.inputStream().use { fis -> fis.copyTo(zos) }
116 }
117 }
118 }
119 lastZipCreated = outputZipFile
120 } catch (e: Exception) {
121 return false
122 }
123 return true
124 }
125
126 /**
127 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
128 */
129 private fun exportSave() {
130 CoroutineScope(Dispatchers.IO).launch {
131 val wasZipCreated = zipSave()
132 val lastZipFile = lastZipCreated
133 if (!wasZipCreated || lastZipFile == null) {
134 withContext(Dispatchers.Main) {
135 Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show()
136 }
137 return@launch
138 }
139
140 withContext(Dispatchers.Main) {
141 val file = DocumentFile.fromSingleUri(
142 context,
143 DocumentsContract.buildDocumentUri(
144 DocumentProvider.AUTHORITY,
145 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
146 )
147 )!!
148 val intent = Intent(Intent.ACTION_SEND)
149 .setDataAndType(file.uri, "application/zip")
150 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
151 .putExtra(Intent.EXTRA_STREAM, file.uri)
152 startForResultExportSave.launch(Intent.createChooser(intent, "Share save file"))
153 }
154 }
155 }
156
157 /**
158 * Imports the save files contained in the zip file, and replaces any existing ones with the new save file.
159 * @param zipUri The Uri of the zip file containing the save file(s) to import.
160 */
161 private fun importSave(zipUri: Uri, activity: AppCompatActivity) {
162 val inputZip = context.contentResolver.openInputStream(zipUri)
163 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
164 var validZip = false
165 val savesFolder = File(savesFolderRoot)
166 val cacheSaveDir = File("${context.cacheDir.path}/saves/")
167 cacheSaveDir.mkdir()
168
169 if (inputZip == null) {
170 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
171 .show()
172 return
173 }
174
175 val filterTitleId =
176 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
177
178 try {
179 CoroutineScope(Dispatchers.IO).launch {
180 FileUtil.unzip(inputZip, cacheSaveDir)
181 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
182 File(savesFolder, savePath).deleteRecursively()
183 File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
184 validZip = true
185 }
186
187 withContext(Dispatchers.Main) {
188 if (!validZip) {
189 MessageDialogFragment.newInstance(
190 titleId = R.string.save_file_invalid_zip_structure,
191 descriptionId = R.string.save_file_invalid_zip_structure_description
192 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
193 return@withContext
194 }
195 Toast.makeText(
196 context,
197 context.getString(R.string.save_file_imported_success),
198 Toast.LENGTH_LONG
199 ).show()
200 }
201
202 cacheSaveDir.deleteRecursively()
203 }
204 } catch (e: Exception) {
205 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
206 .show()
207 }
208 }
209
210 companion object {
211 const val TAG = "ImportExportSavesFragment"
212 }
213}
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 ea8eb073a..f128deda8 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
@@ -5,7 +5,11 @@ package org.yuzu.yuzu_emu.fragments
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.os.Bundle 7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
8import android.widget.Toast 11import android.widget.Toast
12import androidx.appcompat.app.AlertDialog
9import androidx.appcompat.app.AppCompatActivity 13import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 14import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
@@ -15,63 +19,112 @@ import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle 19import androidx.lifecycle.repeatOnLifecycle
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 20import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch 21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
18import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 23import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
19import org.yuzu.yuzu_emu.model.TaskViewModel 24import org.yuzu.yuzu_emu.model.TaskViewModel
20 25
21class IndeterminateProgressDialogFragment : DialogFragment() { 26class IndeterminateProgressDialogFragment : DialogFragment() {
22 private val taskViewModel: TaskViewModel by activityViewModels() 27 private val taskViewModel: TaskViewModel by activityViewModels()
23 28
29 private lateinit var binding: DialogProgressBarBinding
30
24 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 31 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
25 val titleId = requireArguments().getInt(TITLE) 32 val titleId = requireArguments().getInt(TITLE)
33 val cancellable = requireArguments().getBoolean(CANCELLABLE)
26 34
27 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater) 35 binding = DialogProgressBarBinding.inflate(layoutInflater)
28 progressBinding.progressBar.isIndeterminate = true 36 binding.progressBar.isIndeterminate = true
29 val dialog = MaterialAlertDialogBuilder(requireContext()) 37 val dialog = MaterialAlertDialogBuilder(requireContext())
30 .setTitle(titleId) 38 .setTitle(titleId)
31 .setView(progressBinding.root) 39 .setView(binding.root)
32 .create() 40
33 dialog.setCanceledOnTouchOutside(false) 41 if (cancellable) {
34 42 dialog.setNegativeButton(android.R.string.cancel, null)
35 viewLifecycleOwner.lifecycleScope.launch { 43 }
36 repeatOnLifecycle(Lifecycle.State.CREATED) { 44
37 taskViewModel.isComplete.collect { 45 val alertDialog = dialog.create()
38 if (it) { 46 alertDialog.setCanceledOnTouchOutside(false)
39 dialog.dismiss() 47
40 when (val result = taskViewModel.result.value) { 48 if (!taskViewModel.isRunning.value) {
41 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) 49 taskViewModel.runTask()
42 .show() 50 }
43 51 return alertDialog
44 is MessageDialogFragment -> result.show( 52 }
45 requireActivity().supportFragmentManager, 53
46 MessageDialogFragment.TAG 54 override fun onCreateView(
47 ) 55 inflater: LayoutInflater,
56 container: ViewGroup?,
57 savedInstanceState: Bundle?
58 ): View {
59 return binding.root
60 }
61
62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
63 super.onViewCreated(view, savedInstanceState)
64 viewLifecycleOwner.lifecycleScope.apply {
65 launch {
66 repeatOnLifecycle(Lifecycle.State.CREATED) {
67 taskViewModel.isComplete.collect {
68 if (it) {
69 dismiss()
70 when (val result = taskViewModel.result.value) {
71 is String -> Toast.makeText(
72 requireContext(),
73 result,
74 Toast.LENGTH_LONG
75 ).show()
76
77 is MessageDialogFragment -> result.show(
78 requireActivity().supportFragmentManager,
79 MessageDialogFragment.TAG
80 )
81 }
82 taskViewModel.clear()
83 }
84 }
85 }
86 }
87 launch {
88 repeatOnLifecycle(Lifecycle.State.CREATED) {
89 taskViewModel.cancelled.collect {
90 if (it) {
91 dialog?.setTitle(R.string.cancelling)
48 } 92 }
49 taskViewModel.clear()
50 } 93 }
51 } 94 }
52 } 95 }
53 } 96 }
97 }
54 98
55 if (!taskViewModel.isRunning.value) { 99 // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
56 taskViewModel.runTask() 100 // Setting the OnClickListener again after the dialog is shown overrides this behavior.
101 override fun onResume() {
102 super.onResume()
103 val alertDialog = dialog as AlertDialog
104 val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
105 negativeButton.setOnClickListener {
106 alertDialog.setTitle(getString(R.string.cancelling))
107 taskViewModel.setCancelled(true)
57 } 108 }
58 return dialog
59 } 109 }
60 110
61 companion object { 111 companion object {
62 const val TAG = "IndeterminateProgressDialogFragment" 112 const val TAG = "IndeterminateProgressDialogFragment"
63 113
64 private const val TITLE = "Title" 114 private const val TITLE = "Title"
115 private const val CANCELLABLE = "Cancellable"
65 116
66 fun newInstance( 117 fun newInstance(
67 activity: AppCompatActivity, 118 activity: AppCompatActivity,
68 titleId: Int, 119 titleId: Int,
120 cancellable: Boolean = false,
69 task: () -> Any 121 task: () -> Any
70 ): IndeterminateProgressDialogFragment { 122 ): IndeterminateProgressDialogFragment {
71 val dialog = IndeterminateProgressDialogFragment() 123 val dialog = IndeterminateProgressDialogFragment()
72 val args = Bundle() 124 val args = Bundle()
73 ViewModelProvider(activity)[TaskViewModel::class.java].task = task 125 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
74 args.putInt(TITLE, titleId) 126 args.putInt(TITLE, titleId)
127 args.putBoolean(CANCELLABLE, cancellable)
75 dialog.arguments = args 128 dialog.arguments = args
76 return dialog 129 return dialog
77 } 130 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
new file mode 100644
index 000000000..ec116ab62
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.core.view.ViewCompat
11import androidx.core.view.WindowInsetsCompat
12import androidx.core.view.updatePadding
13import androidx.fragment.app.Fragment
14import androidx.fragment.app.activityViewModels
15import androidx.navigation.findNavController
16import androidx.recyclerview.widget.GridLayoutManager
17import com.google.android.material.transition.MaterialSharedAxis
18import org.yuzu.yuzu_emu.R
19import org.yuzu.yuzu_emu.adapters.InstallableAdapter
20import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
21import org.yuzu.yuzu_emu.model.HomeViewModel
22import org.yuzu.yuzu_emu.model.Installable
23import org.yuzu.yuzu_emu.ui.main.MainActivity
24
25class InstallableFragment : Fragment() {
26 private var _binding: FragmentInstallablesBinding? = null
27 private val binding get() = _binding!!
28
29 private val homeViewModel: HomeViewModel by activityViewModels()
30
31 override fun onCreate(savedInstanceState: Bundle?) {
32 super.onCreate(savedInstanceState)
33 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
34 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
35 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
36 }
37
38 override fun onCreateView(
39 inflater: LayoutInflater,
40 container: ViewGroup?,
41 savedInstanceState: Bundle?
42 ): View {
43 _binding = FragmentInstallablesBinding.inflate(layoutInflater)
44 return binding.root
45 }
46
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 super.onViewCreated(view, savedInstanceState)
49
50 val mainActivity = requireActivity() as MainActivity
51
52 homeViewModel.setNavigationVisibility(visible = false, animated = true)
53 homeViewModel.setStatusBarShadeVisibility(visible = false)
54
55 binding.toolbarInstallables.setNavigationOnClickListener {
56 binding.root.findNavController().popBackStack()
57 }
58
59 val installables = listOf(
60 Installable(
61 R.string.user_data,
62 R.string.user_data_description,
63 install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
64 export = { mainActivity.exportUserData.launch("export.zip") }
65 ),
66 Installable(
67 R.string.install_game_content,
68 R.string.install_game_content_description,
69 install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
70 ),
71 Installable(
72 R.string.install_firmware,
73 R.string.install_firmware_description,
74 install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
75 ),
76 if (mainActivity.savesFolderRoot != "") {
77 Installable(
78 R.string.manage_save_data,
79 R.string.import_export_saves_description,
80 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
81 export = { mainActivity.exportSave() }
82 )
83 } else {
84 Installable(
85 R.string.manage_save_data,
86 R.string.import_export_saves_description,
87 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }
88 )
89 },
90 Installable(
91 R.string.install_prod_keys,
92 R.string.install_prod_keys_description,
93 install = { mainActivity.getProdKey.launch(arrayOf("*/*")) }
94 ),
95 Installable(
96 R.string.install_amiibo_keys,
97 R.string.install_amiibo_keys_description,
98 install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
99 )
100 )
101
102 binding.listInstallables.apply {
103 layoutManager = GridLayoutManager(
104 requireContext(),
105 resources.getInteger(R.integer.grid_columns)
106 )
107 adapter = InstallableAdapter(installables)
108 }
109
110 setInsets()
111 }
112
113 private fun setInsets() =
114 ViewCompat.setOnApplyWindowInsetsListener(
115 binding.root
116 ) { _: View, windowInsets: WindowInsetsCompat ->
117 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
118 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
119
120 val leftInsets = barInsets.left + cutoutInsets.left
121 val rightInsets = barInsets.right + cutoutInsets.right
122
123 val mlpAppBar = binding.toolbarInstallables.layoutParams as ViewGroup.MarginLayoutParams
124 mlpAppBar.leftMargin = leftInsets
125 mlpAppBar.rightMargin = rightInsets
126 binding.toolbarInstallables.layoutParams = mlpAppBar
127
128 val mlpScrollAbout =
129 binding.listInstallables.layoutParams as ViewGroup.MarginLayoutParams
130 mlpScrollAbout.leftMargin = leftInsets
131 mlpScrollAbout.rightMargin = rightInsets
132 binding.listInstallables.layoutParams = mlpScrollAbout
133
134 binding.listInstallables.updatePadding(bottom = barInsets.bottom)
135
136 windowInsets
137 }
138}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 7d1c2c8dd..541b22f47 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -4,14 +4,21 @@
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.content.DialogInterface
7import android.content.Intent 8import android.content.Intent
8import android.net.Uri 9import android.net.Uri
9import android.os.Bundle 10import android.os.Bundle
10import androidx.fragment.app.DialogFragment 11import androidx.fragment.app.DialogFragment
12import androidx.fragment.app.FragmentActivity
13import androidx.fragment.app.activityViewModels
14import androidx.lifecycle.ViewModelProvider
11import com.google.android.material.dialog.MaterialAlertDialogBuilder 15import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R 16import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.model.MessageDialogViewModel
13 18
14class MessageDialogFragment : DialogFragment() { 19class MessageDialogFragment : DialogFragment() {
20 private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
21
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 22 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE_ID) 23 val titleId = requireArguments().getInt(TITLE_ID)
17 val titleString = requireArguments().getString(TITLE_STRING)!! 24 val titleString = requireArguments().getString(TITLE_STRING)!!
@@ -37,6 +44,12 @@ class MessageDialogFragment : DialogFragment() {
37 return dialog.show() 44 return dialog.show()
38 } 45 }
39 46
47 override fun onDismiss(dialog: DialogInterface) {
48 super.onDismiss(dialog)
49 messageDialogViewModel.dismissAction.invoke()
50 messageDialogViewModel.clear()
51 }
52
40 private fun openLink(link: String) { 53 private fun openLink(link: String) {
41 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) 54 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
42 startActivity(intent) 55 startActivity(intent)
@@ -52,11 +65,13 @@ class MessageDialogFragment : DialogFragment() {
52 private const val HELP_LINK = "Link" 65 private const val HELP_LINK = "Link"
53 66
54 fun newInstance( 67 fun newInstance(
68 activity: FragmentActivity,
55 titleId: Int = 0, 69 titleId: Int = 0,
56 titleString: String = "", 70 titleString: String = "",
57 descriptionId: Int = 0, 71 descriptionId: Int = 0,
58 descriptionString: String = "", 72 descriptionString: String = "",
59 helpLinkId: Int = 0 73 helpLinkId: Int = 0,
74 dismissAction: () -> Unit = {}
60 ): MessageDialogFragment { 75 ): MessageDialogFragment {
61 val dialog = MessageDialogFragment() 76 val dialog = MessageDialogFragment()
62 val bundle = Bundle() 77 val bundle = Bundle()
@@ -67,6 +82,8 @@ class MessageDialogFragment : DialogFragment() {
67 putString(DESCRIPTION_STRING, descriptionString) 82 putString(DESCRIPTION_STRING, descriptionString)
68 putInt(HELP_LINK, helpLinkId) 83 putInt(HELP_LINK, helpLinkId)
69 } 84 }
85 ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
86 dismissAction
70 dialog.arguments = bundle 87 dialog.arguments = bundle
71 return dialog 88 return dialog
72 } 89 }
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 fbb2f6e18..c66bb635a 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
@@ -295,8 +295,10 @@ class SetupFragment : Fragment() {
295 295
296 override fun onSaveInstanceState(outState: Bundle) { 296 override fun onSaveInstanceState(outState: Bundle) {
297 super.onSaveInstanceState(outState) 297 super.onSaveInstanceState(outState)
298 outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible) 298 if (_binding != null) {
299 outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible) 299 outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
300 outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
301 }
300 outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned) 302 outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
301 } 303 }
302 304
@@ -353,11 +355,15 @@ class SetupFragment : Fragment() {
353 } 355 }
354 356
355 fun pageForward() { 357 fun pageForward() {
356 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 358 if (_binding != null) {
359 binding.viewPager2.currentItem += 1
360 }
357 } 361 }
358 362
359 fun pageBackward() { 363 fun pageBackward() {
360 binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1 364 if (_binding != null) {
365 binding.viewPager2.currentItem -= 1
366 }
361 } 367 }
362 368
363 fun setPageWarned(page: Int) { 369 fun setPageWarned(page: Int) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
new file mode 100644
index 000000000..36a7c97b8
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.annotation.StringRes
7
8data class Installable(
9 @StringRes val titleId: Int,
10 @StringRes val descriptionId: Int,
11 val install: (() -> Unit)? = null,
12 val export: (() -> Unit)? = null
13)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
new file mode 100644
index 000000000..36ffd08d2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7
8class MessageDialogViewModel : ViewModel() {
9 var dismissAction: () -> Unit = {}
10
11 fun clear() {
12 dismissAction = {}
13 }
14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 531c2aaf0..16a794dee 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -20,12 +20,20 @@ class TaskViewModel : ViewModel() {
20 val isRunning: StateFlow<Boolean> get() = _isRunning 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 private val _isRunning = MutableStateFlow(false) 21 private val _isRunning = MutableStateFlow(false)
22 22
23 val cancelled: StateFlow<Boolean> get() = _cancelled
24 private val _cancelled = MutableStateFlow(false)
25
23 lateinit var task: () -> Any 26 lateinit var task: () -> Any
24 27
25 fun clear() { 28 fun clear() {
26 _result.value = Any() 29 _result.value = Any()
27 _isComplete.value = false 30 _isComplete.value = false
28 _isRunning.value = false 31 _isRunning.value = false
32 _cancelled.value = false
33 }
34
35 fun setCancelled(value: Boolean) {
36 _cancelled.value = value
29 } 37 }
30 38
31 fun runTask() { 39 fun runTask() {
@@ -42,3 +50,9 @@ class TaskViewModel : ViewModel() {
42 } 50 }
43 } 51 }
44} 52}
53
54enum class TaskState {
55 Completed,
56 Failed,
57 Cancelled
58}
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 c055c2e35..a13faf3c7 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
@@ -352,7 +352,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
352 } 352 }
353 353
354 private fun addOverlayControls(layout: String) { 354 private fun addOverlayControls(layout: String) {
355 val windowSize = getSafeScreenSize(context) 355 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { 356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
357 overlayButtons.add( 357 overlayButtons.add(
358 initializeOverlayButton( 358 initializeOverlayButton(
@@ -593,7 +593,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
593 } 593 }
594 594
595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { 595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
596 val windowSize = getSafeScreenSize(context) 596 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
597 val min = windowSize.first 597 val min = windowSize.first
598 val max = windowSize.second 598 val max = windowSize.second
599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
@@ -968,14 +968,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
968 * @return A pair of points, the first being the top left corner of the safe area, 968 * @return A pair of points, the first being the top left corner of the safe area,
969 * the second being the bottom right corner of the safe area 969 * the second being the bottom right corner of the safe area
970 */ 970 */
971 private fun getSafeScreenSize(context: Context): Pair<Point, Point> { 971 private fun getSafeScreenSize(
972 context: Context,
973 screenSize: Pair<Int, Int>
974 ): Pair<Point, Point> {
972 // Get screen size 975 // Get screen size
973 val windowMetrics = WindowMetricsCalculator.getOrCreate() 976 val windowMetrics = WindowMetricsCalculator.getOrCreate()
974 .computeCurrentWindowMetrics(context as Activity) 977 .computeCurrentWindowMetrics(context as Activity)
975 var maxY = windowMetrics.bounds.height().toFloat() 978 var maxX = screenSize.first.toFloat()
976 var maxX = windowMetrics.bounds.width().toFloat() 979 var maxY = screenSize.second.toFloat()
977 var minY = 0
978 var minX = 0 980 var minX = 0
981 var minY = 0
979 982
980 // If we have API access, calculate the safe area to draw the overlay 983 // If we have API access, calculate the safe area to draw the overlay
981 var cutoutLeft = 0 984 var cutoutLeft = 0
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 b6b6c6c17..0fa5df5e5 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
@@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.ui.main
6import android.content.Intent 6import android.content.Intent
7import android.net.Uri 7import android.net.Uri
8import android.os.Bundle 8import android.os.Bundle
9import android.provider.DocumentsContract
9import android.view.View 10import android.view.View
10import android.view.ViewGroup.MarginLayoutParams 11import android.view.ViewGroup.MarginLayoutParams
11import android.view.WindowManager 12import android.view.WindowManager
@@ -19,6 +20,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 20import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 21import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 22import androidx.core.view.WindowInsetsCompat
23import androidx.documentfile.provider.DocumentFile
22import androidx.lifecycle.Lifecycle 24import androidx.lifecycle.Lifecycle
23import androidx.lifecycle.lifecycleScope 25import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle 26import androidx.lifecycle.repeatOnLifecycle
@@ -29,6 +31,7 @@ import androidx.preference.PreferenceManager
29import com.google.android.material.color.MaterialColors 31import com.google.android.material.color.MaterialColors
30import com.google.android.material.dialog.MaterialAlertDialogBuilder 32import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.navigation.NavigationBarView 33import com.google.android.material.navigation.NavigationBarView
34import kotlinx.coroutines.CoroutineScope
32import java.io.File 35import java.io.File
33import java.io.FilenameFilter 36import java.io.FilenameFilter
34import java.io.IOException 37import java.io.IOException
@@ -41,21 +44,40 @@ import org.yuzu.yuzu_emu.R
41import org.yuzu.yuzu_emu.activities.EmulationActivity 44import org.yuzu.yuzu_emu.activities.EmulationActivity
42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 45import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 46import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
47import org.yuzu.yuzu_emu.features.DocumentProvider
44import org.yuzu.yuzu_emu.features.settings.model.Settings 48import org.yuzu.yuzu_emu.features.settings.model.Settings
45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 49import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 50import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
51import org.yuzu.yuzu_emu.getPublicFilesDir
47import org.yuzu.yuzu_emu.model.GamesViewModel 52import org.yuzu.yuzu_emu.model.GamesViewModel
48import org.yuzu.yuzu_emu.model.HomeViewModel 53import org.yuzu.yuzu_emu.model.HomeViewModel
54import org.yuzu.yuzu_emu.model.TaskState
55import org.yuzu.yuzu_emu.model.TaskViewModel
49import org.yuzu.yuzu_emu.utils.* 56import org.yuzu.yuzu_emu.utils.*
57import java.io.BufferedInputStream
58import java.io.BufferedOutputStream
59import java.io.FileOutputStream
60import java.time.LocalDateTime
61import java.time.format.DateTimeFormatter
62import java.util.zip.ZipEntry
63import java.util.zip.ZipInputStream
50 64
51class MainActivity : AppCompatActivity(), ThemeProvider { 65class MainActivity : AppCompatActivity(), ThemeProvider {
52 private lateinit var binding: ActivityMainBinding 66 private lateinit var binding: ActivityMainBinding
53 67
54 private val homeViewModel: HomeViewModel by viewModels() 68 private val homeViewModel: HomeViewModel by viewModels()
55 private val gamesViewModel: GamesViewModel by viewModels() 69 private val gamesViewModel: GamesViewModel by viewModels()
70 private val taskViewModel: TaskViewModel by viewModels()
56 71
57 override var themeId: Int = 0 72 override var themeId: Int = 0
58 73
74 private val savesFolder
75 get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
76
77 // Get first subfolder in saves folder (should be the user folder)
78 val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
79 private var lastZipCreated: File? = null
80
59 override fun onCreate(savedInstanceState: Bundle?) { 81 override fun onCreate(savedInstanceState: Bundle?) {
60 val splashScreen = installSplashScreen() 82 val splashScreen = installSplashScreen()
61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } 83 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -307,6 +329,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
307 fun processKey(result: Uri): Boolean { 329 fun processKey(result: Uri): Boolean {
308 if (FileUtil.getExtension(result) != "keys") { 330 if (FileUtil.getExtension(result) != "keys") {
309 MessageDialogFragment.newInstance( 331 MessageDialogFragment.newInstance(
332 this,
310 titleId = R.string.reading_keys_failure, 333 titleId = R.string.reading_keys_failure,
311 descriptionId = R.string.install_prod_keys_failure_extension_description 334 descriptionId = R.string.install_prod_keys_failure_extension_description
312 ).show(supportFragmentManager, MessageDialogFragment.TAG) 335 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -336,6 +359,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
336 return true 359 return true
337 } else { 360 } else {
338 MessageDialogFragment.newInstance( 361 MessageDialogFragment.newInstance(
362 this,
339 titleId = R.string.invalid_keys_error, 363 titleId = R.string.invalid_keys_error,
340 descriptionId = R.string.install_keys_failure_description, 364 descriptionId = R.string.install_keys_failure_description,
341 helpLinkId = R.string.dumping_keys_quickstart_link 365 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -371,11 +395,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
371 val task: () -> Any = { 395 val task: () -> Any = {
372 var messageToShow: Any 396 var messageToShow: Any
373 try { 397 try {
374 FileUtil.unzip(inputZip, cacheFirmwareDir) 398 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
375 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 399 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
376 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 400 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
377 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { 401 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
378 MessageDialogFragment.newInstance( 402 MessageDialogFragment.newInstance(
403 this,
379 titleId = R.string.firmware_installed_failure, 404 titleId = R.string.firmware_installed_failure,
380 descriptionId = R.string.firmware_installed_failure_description 405 descriptionId = R.string.firmware_installed_failure_description
381 ) 406 )
@@ -395,7 +420,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
395 IndeterminateProgressDialogFragment.newInstance( 420 IndeterminateProgressDialogFragment.newInstance(
396 this, 421 this,
397 R.string.firmware_installing, 422 R.string.firmware_installing,
398 task 423 task = task
399 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 424 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
400 } 425 }
401 426
@@ -407,6 +432,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
407 432
408 if (FileUtil.getExtension(result) != "bin") { 433 if (FileUtil.getExtension(result) != "bin") {
409 MessageDialogFragment.newInstance( 434 MessageDialogFragment.newInstance(
435 this,
410 titleId = R.string.reading_keys_failure, 436 titleId = R.string.reading_keys_failure,
411 descriptionId = R.string.install_amiibo_keys_failure_extension_description 437 descriptionId = R.string.install_amiibo_keys_failure_extension_description
412 ).show(supportFragmentManager, MessageDialogFragment.TAG) 438 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -434,6 +460,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
434 ).show() 460 ).show()
435 } else { 461 } else {
436 MessageDialogFragment.newInstance( 462 MessageDialogFragment.newInstance(
463 this,
437 titleId = R.string.invalid_keys_error, 464 titleId = R.string.invalid_keys_error,
438 descriptionId = R.string.install_keys_failure_description, 465 descriptionId = R.string.install_keys_failure_description,
439 helpLinkId = R.string.dumping_keys_quickstart_link 466 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -501,7 +528,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
501 if (documents.isNotEmpty()) { 528 if (documents.isNotEmpty()) {
502 IndeterminateProgressDialogFragment.newInstance( 529 IndeterminateProgressDialogFragment.newInstance(
503 this@MainActivity, 530 this@MainActivity,
504 R.string.install_game_content 531 R.string.installing_game_content
505 ) { 532 ) {
506 var installSuccess = 0 533 var installSuccess = 0
507 var installOverwrite = 0 534 var installOverwrite = 0
@@ -509,7 +536,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
509 var errorExtension = 0 536 var errorExtension = 0
510 var errorOther = 0 537 var errorOther = 0
511 documents.forEach { 538 documents.forEach {
512 when (NativeLibrary.installFileToNand(it.toString())) { 539 when (
540 NativeLibrary.installFileToNand(
541 it.toString(),
542 FileUtil.getExtension(it)
543 )
544 ) {
513 NativeLibrary.InstallFileToNandResult.Success -> { 545 NativeLibrary.InstallFileToNandResult.Success -> {
514 installSuccess += 1 546 installSuccess += 1
515 } 547 }
@@ -583,12 +615,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
583 installResult.append(separator) 615 installResult.append(separator)
584 } 616 }
585 return@newInstance MessageDialogFragment.newInstance( 617 return@newInstance MessageDialogFragment.newInstance(
618 this,
586 titleId = R.string.install_game_content_failure, 619 titleId = R.string.install_game_content_failure,
587 descriptionString = installResult.toString().trim(), 620 descriptionString = installResult.toString().trim(),
588 helpLinkId = R.string.install_game_content_help_link 621 helpLinkId = R.string.install_game_content_help_link
589 ) 622 )
590 } else { 623 } else {
591 return@newInstance MessageDialogFragment.newInstance( 624 return@newInstance MessageDialogFragment.newInstance(
625 this,
592 titleId = R.string.install_game_content_success, 626 titleId = R.string.install_game_content_success,
593 descriptionString = installResult.toString().trim() 627 descriptionString = installResult.toString().trim()
594 ) 628 )
@@ -596,4 +630,228 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
596 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 630 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
597 } 631 }
598 } 632 }
633
634 val exportUserData = registerForActivityResult(
635 ActivityResultContracts.CreateDocument("application/zip")
636 ) { result ->
637 if (result == null) {
638 return@registerForActivityResult
639 }
640
641 IndeterminateProgressDialogFragment.newInstance(
642 this,
643 R.string.exporting_user_data,
644 true
645 ) {
646 val zipResult = FileUtil.zipFromInternalStorage(
647 File(DirectoryInitialization.userDirectory!!),
648 DirectoryInitialization.userDirectory!!,
649 BufferedOutputStream(contentResolver.openOutputStream(result)),
650 taskViewModel.cancelled
651 )
652 return@newInstance when (zipResult) {
653 TaskState.Completed -> getString(R.string.user_data_export_success)
654 TaskState.Failed -> R.string.export_failed
655 TaskState.Cancelled -> R.string.user_data_export_cancelled
656 }
657 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
658 }
659
660 val importUserData =
661 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
662 if (result == null) {
663 return@registerForActivityResult
664 }
665
666 IndeterminateProgressDialogFragment.newInstance(
667 this,
668 R.string.importing_user_data
669 ) {
670 val checkStream =
671 ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
672 var isYuzuBackup = false
673 checkStream.use { stream ->
674 var ze: ZipEntry? = null
675 while (stream.nextEntry?.also { ze = it } != null) {
676 val itemName = ze!!.name.trim()
677 if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
678 isYuzuBackup = true
679 return@use
680 }
681 }
682 }
683 if (!isYuzuBackup) {
684 return@newInstance MessageDialogFragment.newInstance(
685 this,
686 titleId = R.string.invalid_yuzu_backup,
687 descriptionId = R.string.user_data_import_failed_description
688 )
689 }
690
691 // Clear existing user data
692 File(DirectoryInitialization.userDirectory!!).deleteRecursively()
693
694 // Copy archive to internal storage
695 try {
696 FileUtil.unzipToInternalStorage(
697 BufferedInputStream(contentResolver.openInputStream(result)),
698 File(DirectoryInitialization.userDirectory!!)
699 )
700 } catch (e: Exception) {
701 return@newInstance MessageDialogFragment.newInstance(
702 this,
703 titleId = R.string.import_failed,
704 descriptionId = R.string.user_data_import_failed_description
705 )
706 }
707
708 // Reinitialize relevant data
709 NativeLibrary.initializeEmulation()
710 gamesViewModel.reloadGames(false)
711
712 return@newInstance getString(R.string.user_data_import_success)
713 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
714 }
715
716 /**
717 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
718 * @return true if the zip file is successfully created, false otherwise.
719 */
720 private fun zipSave(): Boolean {
721 try {
722 val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
723 tempFolder.mkdirs()
724 val saveFolder = File(savesFolderRoot)
725 val outputZipFile = File(
726 tempFolder,
727 "yuzu saves - ${
728 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
729 }.zip"
730 )
731 outputZipFile.createNewFile()
732 val result = FileUtil.zipFromInternalStorage(
733 saveFolder,
734 savesFolderRoot,
735 BufferedOutputStream(FileOutputStream(outputZipFile))
736 )
737 if (result == TaskState.Failed) {
738 return false
739 }
740 lastZipCreated = outputZipFile
741 } catch (e: Exception) {
742 return false
743 }
744 return true
745 }
746
747 /**
748 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
749 */
750 fun exportSave() {
751 CoroutineScope(Dispatchers.IO).launch {
752 val wasZipCreated = zipSave()
753 val lastZipFile = lastZipCreated
754 if (!wasZipCreated || lastZipFile == null) {
755 withContext(Dispatchers.Main) {
756 Toast.makeText(
757 this@MainActivity,
758 getString(R.string.export_save_failed),
759 Toast.LENGTH_LONG
760 ).show()
761 }
762 return@launch
763 }
764
765 withContext(Dispatchers.Main) {
766 val file = DocumentFile.fromSingleUri(
767 this@MainActivity,
768 DocumentsContract.buildDocumentUri(
769 DocumentProvider.AUTHORITY,
770 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
771 )
772 )!!
773 val intent = Intent(Intent.ACTION_SEND)
774 .setDataAndType(file.uri, "application/zip")
775 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
776 .putExtra(Intent.EXTRA_STREAM, file.uri)
777 startForResultExportSave.launch(
778 Intent.createChooser(
779 intent,
780 getString(R.string.share_save_file)
781 )
782 )
783 }
784 }
785 }
786
787 private val startForResultExportSave =
788 registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
789 File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
790 }
791
792 val importSaves =
793 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
794 if (result == null) {
795 return@registerForActivityResult
796 }
797
798 NativeLibrary.initializeEmptyUserDirectory()
799
800 val inputZip = contentResolver.openInputStream(result)
801 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
802 var validZip = false
803 val savesFolder = File(savesFolderRoot)
804 val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/")
805 cacheSaveDir.mkdir()
806
807 if (inputZip == null) {
808 Toast.makeText(
809 applicationContext,
810 getString(R.string.fatal_error),
811 Toast.LENGTH_LONG
812 ).show()
813 return@registerForActivityResult
814 }
815
816 val filterTitleId =
817 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
818
819 try {
820 CoroutineScope(Dispatchers.IO).launch {
821 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
822 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
823 File(savesFolder, savePath).deleteRecursively()
824 File(cacheSaveDir, savePath).copyRecursively(
825 File(savesFolder, savePath),
826 true
827 )
828 validZip = true
829 }
830
831 withContext(Dispatchers.Main) {
832 if (!validZip) {
833 MessageDialogFragment.newInstance(
834 this@MainActivity,
835 titleId = R.string.save_file_invalid_zip_structure,
836 descriptionId = R.string.save_file_invalid_zip_structure_description
837 ).show(supportFragmentManager, MessageDialogFragment.TAG)
838 return@withContext
839 }
840 Toast.makeText(
841 applicationContext,
842 getString(R.string.save_file_imported_success),
843 Toast.LENGTH_LONG
844 ).show()
845 }
846
847 cacheSaveDir.deleteRecursively()
848 }
849 } catch (e: Exception) {
850 Toast.makeText(
851 applicationContext,
852 getString(R.string.fatal_error),
853 Toast.LENGTH_LONG
854 ).show()
855 }
856 }
599} 857}
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 142af5f26..c3f53f1c5 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
@@ -8,6 +8,7 @@ import android.database.Cursor
8import android.net.Uri 8import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import androidx.documentfile.provider.DocumentFile 10import androidx.documentfile.provider.DocumentFile
11import kotlinx.coroutines.flow.StateFlow
11import java.io.BufferedInputStream 12import java.io.BufferedInputStream
12import java.io.File 13import java.io.File
13import java.io.FileOutputStream 14import java.io.FileOutputStream
@@ -18,6 +19,9 @@ import java.util.zip.ZipEntry
18import java.util.zip.ZipInputStream 19import java.util.zip.ZipInputStream
19import org.yuzu.yuzu_emu.YuzuApplication 20import org.yuzu.yuzu_emu.YuzuApplication
20import org.yuzu.yuzu_emu.model.MinimalDocumentFile 21import org.yuzu.yuzu_emu.model.MinimalDocumentFile
22import org.yuzu.yuzu_emu.model.TaskState
23import java.io.BufferedOutputStream
24import java.util.zip.ZipOutputStream
21 25
22object FileUtil { 26object FileUtil {
23 const val PATH_TREE = "tree" 27 const val PATH_TREE = "tree"
@@ -282,30 +286,65 @@ object FileUtil {
282 286
283 /** 287 /**
284 * Extracts the given zip file into the given directory. 288 * Extracts the given zip file into the given directory.
285 * @exception IOException if the file was being created outside of the target directory
286 */ 289 */
287 @Throws(SecurityException::class) 290 @Throws(SecurityException::class)
288 fun unzip(zipStream: InputStream, destDir: File): Boolean { 291 fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
289 ZipInputStream(BufferedInputStream(zipStream)).use { zis -> 292 ZipInputStream(zipStream).use { zis ->
290 var entry: ZipEntry? = zis.nextEntry 293 var entry: ZipEntry? = zis.nextEntry
291 while (entry != null) { 294 while (entry != null) {
292 val entryName = entry.name 295 val newFile = File(destDir, entry.name)
293 val entryFile = File(destDir, entryName) 296 val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
294 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { 297
295 throw SecurityException("Entry is outside of the target dir: " + entryFile.name) 298 if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
299 throw SecurityException("Zip file attempted path traversal! ${entry.name}")
300 }
301
302 if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
303 throw IOException("Failed to create directory $destinationDirectory")
296 } 304 }
297 if (entry.isDirectory) { 305
298 entryFile.mkdirs() 306 if (!entry.isDirectory) {
299 } else { 307 newFile.outputStream().use { fos -> zis.copyTo(fos) }
300 entryFile.parentFile?.mkdirs()
301 entryFile.createNewFile()
302 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
303 } 308 }
304 entry = zis.nextEntry 309 entry = zis.nextEntry
305 } 310 }
306 } 311 }
312 }
307 313
308 return true 314 /**
315 * Creates a zip file from a directory within internal storage
316 * @param inputFile File representation of the item that will be zipped
317 * @param rootDir Directory containing the inputFile
318 * @param outputStream Stream where the zip file will be output
319 */
320 fun zipFromInternalStorage(
321 inputFile: File,
322 rootDir: String,
323 outputStream: BufferedOutputStream,
324 cancelled: StateFlow<Boolean>? = null
325 ): TaskState {
326 try {
327 ZipOutputStream(outputStream).use { zos ->
328 inputFile.walkTopDown().forEach { file ->
329 if (cancelled?.value == true) {
330 return TaskState.Cancelled
331 }
332
333 if (!file.isDirectory) {
334 val entryName =
335 file.absolutePath.removePrefix(rootDir).removePrefix("/")
336 val entry = ZipEntry(entryName)
337 zos.putNextEntry(entry)
338 if (file.isFile) {
339 file.inputStream().use { fis -> fis.copyTo(zos) }
340 }
341 }
342 }
343 }
344 } catch (e: Exception) {
345 return TaskState.Failed
346 }
347 return TaskState.Completed
309 } 348 }
310 349
311 fun isRootTreeUri(uri: Uri): Boolean { 350 fun isRootTreeUri(uri: Uri): Boolean {
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 34b425cb4..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -282,7 +282,7 @@ void Config::ReadValues() {
282 std::stringstream ss(title_list); 282 std::stringstream ss(title_list);
283 std::string line; 283 std::string line;
284 while (std::getline(ss, line, '|')) { 284 while (std::getline(ss, line, '|')) {
285 const auto title_id = std::stoul(line, nullptr, 16); 285 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); 286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
287 287
288 std::stringstream inner_ss(disabled_list); 288 std::stringstream inner_ss(disabled_list);
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index a890c6604..a7e414b81 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -11,6 +11,12 @@
11#include "jni/emu_window/emu_window.h" 11#include "jni/emu_window/emu_window.h"
12 12
13void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { 13void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
14 m_window_width = ANativeWindow_getWidth(surface);
15 m_window_height = ANativeWindow_getHeight(surface);
16
17 // Ensures that we emulate with the correct aspect ratio.
18 UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
19
14 window_info.render_surface = reinterpret_cast<void*>(surface); 20 window_info.render_surface = reinterpret_cast<void*>(surface);
15} 21}
16 22
@@ -62,14 +68,8 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste
62 return; 68 return;
63 } 69 }
64 70
65 m_window_width = ANativeWindow_getWidth(surface); 71 OnSurfaceChanged(surface);
66 m_window_height = ANativeWindow_getHeight(surface);
67
68 // Ensures that we emulate with the correct aspect ratio.
69 UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
70
71 window_info.type = Core::Frontend::WindowSystemType::Android; 72 window_info.type = Core::Frontend::WindowSystemType::Android;
72 window_info.render_surface = reinterpret_cast<void*>(surface);
73 73
74 m_input_subsystem->Initialize(); 74 m_input_subsystem->Initialize();
75} 75}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index f31fe054b..9cf71680c 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,8 @@
13 13
14#include <android/api-level.h> 14#include <android/api-level.h>
15#include <android/native_window_jni.h> 15#include <android/native_window_jni.h>
16#include <common/fs/fs.h>
17#include <core/file_sys/savedata_factory.h>
16#include <core/loader/nro.h> 18#include <core/loader/nro.h>
17#include <jni.h> 19#include <jni.h>
18 20
@@ -102,7 +104,7 @@ public:
102 m_native_window = native_window; 104 m_native_window = native_window;
103 } 105 }
104 106
105 int InstallFileToNand(std::string filename) { 107 int InstallFileToNand(std::string filename, std::string file_extension) {
106 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, 108 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
107 std::size_t block_size) { 109 std::size_t block_size) {
108 if (src == nullptr || dest == nullptr) { 110 if (src == nullptr || dest == nullptr) {
@@ -134,15 +136,11 @@ public:
134 m_system.GetFileSystemController().CreateFactories(*m_vfs); 136 m_system.GetFileSystemController().CreateFactories(*m_vfs);
135 137
136 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; 138 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
137 if (filename.ends_with("nsp")) { 139 if (file_extension == "nsp") {
138 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); 140 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
139 if (nsp->IsExtractedType()) { 141 if (nsp->IsExtractedType()) {
140 return InstallError; 142 return InstallError;
141 } 143 }
142 } else if (filename.ends_with("xci")) {
143 jconst xci =
144 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
145 nsp = xci->GetSecurePartitionNSP();
146 } else { 144 } else {
147 return ErrorFilenameExtension; 145 return ErrorFilenameExtension;
148 } 146 }
@@ -607,8 +605,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
607} 605}
608 606
609int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, 607int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
610 [[maybe_unused]] jstring j_file) { 608 jstring j_file,
611 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); 609 jstring j_file_extension) {
610 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
611 GetJString(env, j_file_extension));
612} 612}
613 613
614void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, 614void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
@@ -879,4 +879,24 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
879 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); 879 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
880} 880}
881 881
882void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* env,
883 jobject instance) {
884 const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
885 auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
886 Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
887
888 Service::Account::ProfileManager manager;
889 const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
890 ASSERT(user_id);
891
892 const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
893 EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
894 FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
895
896 const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
897 if (!Common::FS::CreateParentDirs(full_path)) {
898 LOG_WARNING(Frontend, "Failed to create full path of the default user's save directory");
899 }
900}
901
882} // extern "C" 902} // extern "C"
diff --git a/src/android/app/src/main/res/drawable/ic_export.xml b/src/android/app/src/main/res/drawable/ic_export.xml
new file mode 100644
index 000000000..463d2f41c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_export.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:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_import.xml b/src/android/app/src/main/res/drawable/ic_import.xml
new file mode 100644
index 000000000..3a99dd5e6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_import.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:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
9</vector>
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 8a026a30a..a187665f2 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -22,7 +22,7 @@
22 22
23 <View 23 <View
24 android:id="@+id/navigation_bar_shade" 24 android:id="@+id/navigation_bar_shade"
25 android:layout_width="match_parent" 25 android:layout_width="0dp"
26 android:layout_height="1px" 26 android:layout_height="1px"
27 android:background="@android:color/transparent" 27 android:background="@android:color/transparent"
28 android:clickable="false" 28 android:clickable="false"
diff --git a/src/android/app/src/main/res/layout/card_installable.xml b/src/android/app/src/main/res/layout/card_installable.xml
new file mode 100644
index 000000000..f5b0e3741
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_installable.xml
@@ -0,0 +1,71 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView 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 style="?attr/materialCardViewOutlinedStyle"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginHorizontal="16dp"
9 android:layout_marginVertical="12dp">
10
11 <LinearLayout
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:layout_margin="16dp"
15 android:orientation="horizontal"
16 android:layout_gravity="center">
17
18 <LinearLayout
19 android:layout_width="0dp"
20 android:layout_height="wrap_content"
21 android:layout_marginEnd="16dp"
22 android:layout_weight="1"
23 android:orientation="vertical">
24
25 <com.google.android.material.textview.MaterialTextView
26 android:id="@+id/title"
27 style="@style/TextAppearance.Material3.TitleMedium"
28 android:layout_width="match_parent"
29 android:layout_height="wrap_content"
30 android:text="@string/user_data"
31 android:textAlignment="viewStart" />
32
33 <com.google.android.material.textview.MaterialTextView
34 android:id="@+id/description"
35 style="@style/TextAppearance.Material3.BodyMedium"
36 android:layout_width="match_parent"
37 android:layout_height="wrap_content"
38 android:layout_marginTop="6dp"
39 android:text="@string/user_data_description"
40 android:textAlignment="viewStart" />
41
42 </LinearLayout>
43
44 <Button
45 android:id="@+id/button_export"
46 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
47 android:layout_width="wrap_content"
48 android:layout_height="wrap_content"
49 android:layout_gravity="center_vertical"
50 android:contentDescription="@string/export"
51 android:tooltipText="@string/export"
52 android:visibility="gone"
53 app:icon="@drawable/ic_export"
54 tools:visibility="visible" />
55
56 <Button
57 android:id="@+id/button_install"
58 style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
59 android:layout_width="wrap_content"
60 android:layout_height="wrap_content"
61 android:layout_gravity="center_vertical"
62 android:layout_marginStart="12dp"
63 android:contentDescription="@string/string_import"
64 android:tooltipText="@string/string_import"
65 android:visibility="gone"
66 app:icon="@drawable/ic_import"
67 tools:visibility="visible" />
68
69 </LinearLayout>
70
71</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
index d17711a65..0209ea082 100644
--- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml
+++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
@@ -1,24 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 xmlns:app="http://schemas.android.com/apk/res-auto" 3 xmlns:app="http://schemas.android.com/apk/res-auto"
6 android:orientation="vertical"> 4 android:id="@+id/progress_bar"
7 5 android:layout_width="match_parent"
8 <com.google.android.material.progressindicator.LinearProgressIndicator 6 android:layout_height="wrap_content"
9 android:id="@+id/progress_bar" 7 android:padding="24dp"
10 android:layout_width="match_parent" 8 app:trackCornerRadius="4dp" />
11 android:layout_height="wrap_content"
12 android:layout_margin="24dp"
13 app:trackCornerRadius="4dp" />
14
15 <TextView
16 android:id="@+id/progress_text"
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"
19 android:layout_marginLeft="24dp"
20 android:layout_marginRight="24dp"
21 android:layout_marginBottom="24dp"
22 android:gravity="end" />
23
24</LinearLayout>
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 da97d85c1..750ce094a 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -32,7 +32,8 @@
32 android:layout_width="wrap_content" 32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content" 33 android:layout_height="wrap_content"
34 android:layout_gravity="center" 34 android:layout_gravity="center"
35 android:focusable="false"> 35 android:focusable="false"
36 android:clickable="false">
36 37
37 <androidx.constraintlayout.widget.ConstraintLayout 38 <androidx.constraintlayout.widget.ConstraintLayout
38 android:id="@+id/loading_layout" 39 android:id="@+id/loading_layout"
@@ -155,7 +156,7 @@
155 android:id="@+id/in_game_menu" 156 android:id="@+id/in_game_menu"
156 android:layout_width="wrap_content" 157 android:layout_width="wrap_content"
157 android:layout_height="match_parent" 158 android:layout_height="match_parent"
158 android:layout_gravity="start|bottom" 159 android:layout_gravity="start"
159 app:headerLayout="@layout/header_in_game" 160 app:headerLayout="@layout/header_in_game"
160 app:menu="@menu/menu_in_game" 161 app:menu="@menu/menu_in_game"
161 tools:visibility="gone" /> 162 tools:visibility="gone" />
diff --git a/src/android/app/src/main/res/layout/fragment_installables.xml b/src/android/app/src/main/res/layout/fragment_installables.xml
new file mode 100644
index 000000000..3a4df81a6
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_installables.xml
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_licenses"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <com.google.android.material.appbar.AppBarLayout
10 android:id="@+id/appbar_installables"
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:fitsSystemWindows="true">
14
15 <com.google.android.material.appbar.MaterialToolbar
16 android:id="@+id/toolbar_installables"
17 android:layout_width="match_parent"
18 android:layout_height="?attr/actionBarSize"
19 app:title="@string/manage_yuzu_data"
20 app:navigationIcon="@drawable/ic_back" />
21
22 </com.google.android.material.appbar.AppBarLayout>
23
24 <androidx.recyclerview.widget.RecyclerView
25 android:id="@+id/list_installables"
26 android:layout_width="match_parent"
27 android:layout_height="match_parent"
28 android:clipToPadding="false"
29 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
30
31</androidx.coordinatorlayout.widget.CoordinatorLayout>
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 2e0ce7a3d..2356b802b 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -19,6 +19,9 @@
19 <action 19 <action
20 android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment" 20 android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment"
21 app:destination="@id/earlyAccessFragment" /> 21 app:destination="@id/earlyAccessFragment" />
22 <action
23 android:id="@+id/action_homeSettingsFragment_to_installableFragment"
24 app:destination="@id/installableFragment" />
22 </fragment> 25 </fragment>
23 26
24 <fragment 27 <fragment
@@ -88,5 +91,9 @@
88 <action 91 <action
89 android:id="@+id/action_global_settingsActivity" 92 android:id="@+id/action_global_settingsActivity"
90 app:destination="@id/settingsActivity" /> 93 app:destination="@id/settingsActivity" />
94 <fragment
95 android:id="@+id/installableFragment"
96 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
97 android:label="InstallableFragment" />
91 98
92</navigation> 99</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index daaa7ffde..dd0f36392 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -79,7 +79,6 @@
79 <string name="manage_save_data">Speicherdaten verwalten</string> 79 <string name="manage_save_data">Speicherdaten verwalten</string>
80 <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string> 80 <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string>
81 <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string> 81 <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string>
82 <string name="import_export_saves_no_profile">Keine Speicherdaten gefunden. Bitte starte ein Spiel und versuche es erneut.</string>
83 <string name="save_file_imported_success">Erfolgreich importiert</string> 82 <string name="save_file_imported_success">Erfolgreich importiert</string>
84 <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string> 83 <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string>
85 <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string> 84 <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index e9129cb00..d398f862f 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Administrar datos de guardado</string> 81 <string name="manage_save_data">Administrar datos de guardado</string>
82 <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string> 82 <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string>
83 <string name="import_export_saves_description">Importar o exportar archivos de guardado</string> 83 <string name="import_export_saves_description">Importar o exportar archivos de guardado</string>
84 <string name="import_export_saves_no_profile">No se han encontrado datos de guardado. Por favor, ejecute un juego y vuelva a intentarlo.</string>
85 <string name="save_file_imported_success">Importado correctamente</string> 84 <string name="save_file_imported_success">Importado correctamente</string>
86 <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string> 85 <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string>
87 <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string> 86 <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index 2d99d618e..a7abd9077 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gérer les données de sauvegarde</string> 81 <string name="manage_save_data">Gérer les données de sauvegarde</string>
82 <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string> 82 <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string>
83 <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string> 83 <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string>
84 <string name="import_export_saves_no_profile">Aucune données de sauvegarde trouvées. Veuillez lancer un jeu et réessayer.</string>
85 <string name="save_file_imported_success">Importé avec succès</string> 84 <string name="save_file_imported_success">Importé avec succès</string>
86 <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string> 85 <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string>
87 <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string> 86 <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index d9c3de385..b18161801 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gestisci i salvataggi</string> 81 <string name="manage_save_data">Gestisci i salvataggi</string>
82 <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string> 82 <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string>
83 <string name="import_export_saves_description">Importa o esporta i salvataggi</string> 83 <string name="import_export_saves_description">Importa o esporta i salvataggi</string>
84 <string name="import_export_saves_no_profile">Nessun salvataggio trovato. Avvia un gioco e riprova.</string>
85 <string name="save_file_imported_success">Importato con successo</string> 84 <string name="save_file_imported_success">Importato con successo</string>
86 <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string> 85 <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string>
87 <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string> 86 <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 7a226cd5c..88fa5a0bb 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -80,7 +80,6 @@
80 <string name="manage_save_data">セーブデータを管理</string> 80 <string name="manage_save_data">セーブデータを管理</string>
81 <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string> 81 <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string>
82 <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string> 82 <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string>
83 <string name="import_export_saves_no_profile">セーブデータがありません。ゲームを起動してから再度お試しください。</string>
84 <string name="save_file_imported_success">インポートが完了しました</string> 83 <string name="save_file_imported_success">インポートが完了しました</string>
85 <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string> 84 <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string>
86 <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string> 85 <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 427b6e5a0..4b658255c 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">저장 데이터 관리</string> 81 <string name="manage_save_data">저장 데이터 관리</string>
82 <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string> 82 <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string>
83 <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string> 83 <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string>
84 <string name="import_export_saves_no_profile">저장 데이터를 찾을 수 없습니다. 게임을 실행한 후 다시 시도하세요.</string>
85 <string name="save_file_imported_success">가져오기 성공</string> 84 <string name="save_file_imported_success">가져오기 성공</string>
86 <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string> 85 <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string>
87 <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string> 86 <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index ce8d7a9e4..dd602a389 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Administrere lagringsdata</string> 81 <string name="manage_save_data">Administrere lagringsdata</string>
82 <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string> 82 <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string>
83 <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string> 83 <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string>
84 <string name="import_export_saves_no_profile">Ingen lagringsdata funnet. Start et nytt spill og prøv på nytt.</string>
85 <string name="save_file_imported_success">Vellykket import</string> 84 <string name="save_file_imported_success">Vellykket import</string>
86 <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string> 85 <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string>
87 <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string> 86 <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index c2c24b48f..2fdd1f952 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Zarządzaj plikami zapisów gier</string> 81 <string name="manage_save_data">Zarządzaj plikami zapisów gier</string>
82 <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string> 82 <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string>
83 <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string> 83 <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string>
84 <string name="import_export_saves_no_profile">Nie znaleziono plików zapisów. Uruchom grę i spróbuj ponownie.</string>
85 <string name="save_file_imported_success">Zaimportowano pomyślnie</string> 84 <string name="save_file_imported_success">Zaimportowano pomyślnie</string>
86 <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string> 85 <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string>
87 <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string> 86 <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 04f276108..2f26367fe 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gerir dados guardados</string> 81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> 82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string> 83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string> 84 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> 85 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> 86 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 66a3a1a2e..4e1eb4cd7 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Gerir dados guardados</string> 81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string> 82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string> 83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string> 84 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string> 85 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string> 86 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index f770e954f..f5695dc93 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Управление данными сохранений</string> 81 <string name="manage_save_data">Управление данными сохранений</string>
82 <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string> 82 <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string>
83 <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string> 83 <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string>
84 <string name="import_export_saves_no_profile">Данные сохранений не найдены. Пожалуйста, запустите игру и повторите попытку.</string>
85 <string name="save_file_imported_success">Успешно импортировано</string> 84 <string name="save_file_imported_success">Успешно импортировано</string>
86 <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string> 85 <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string>
87 <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string> 86 <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index ea3ab1b15..061bc6f04 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">Керування даними збережень</string> 81 <string name="manage_save_data">Керування даними збережень</string>
82 <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string> 82 <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string>
83 <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string> 83 <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string>
84 <string name="import_export_saves_no_profile">Дані збережень не знайдено. Будь ласка, запустіть гру та повторіть спробу.</string>
85 <string name="save_file_imported_success">Успішно імпортовано</string> 84 <string name="save_file_imported_success">Успішно імпортовано</string>
86 <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string> 85 <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string>
87 <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string> 86 <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string>
diff --git a/src/android/app/src/main/res/values-w600dp/integers.xml b/src/android/app/src/main/res/values-w600dp/integers.xml
new file mode 100644
index 000000000..9975db801
--- /dev/null
+++ b/src/android/app/src/main/res/values-w600dp/integers.xml
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <integer name="grid_columns">2</integer>
5
6</resources>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index b45a5a528..fe6dd5eaa 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">管理存档数据</string> 81 <string name="manage_save_data">管理存档数据</string>
82 <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string> 82 <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string>
83 <string name="import_export_saves_description">导入或导出存档</string> 83 <string name="import_export_saves_description">导入或导出存档</string>
84 <string name="import_export_saves_no_profile">找不到存档数据,请启动游戏并重试。</string>
85 <string name="save_file_imported_success">已成功导入存档</string> 84 <string name="save_file_imported_success">已成功导入存档</string>
86 <string name="save_file_invalid_zip_structure">无效的存档目录</string> 85 <string name="save_file_invalid_zip_structure">无效的存档目录</string>
87 <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string> 86 <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 3aab889e4..9b3e54224 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -81,7 +81,6 @@
81 <string name="manage_save_data">管理儲存資料</string> 81 <string name="manage_save_data">管理儲存資料</string>
82 <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string> 82 <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string>
83 <string name="import_export_saves_description">匯入或匯出儲存檔案</string> 83 <string name="import_export_saves_description">匯入或匯出儲存檔案</string>
84 <string name="import_export_saves_no_profile">找不到儲存資料,請啟動遊戲並重試。</string>
85 <string name="save_file_imported_success">已成功匯入</string> 84 <string name="save_file_imported_success">已成功匯入</string>
86 <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string> 85 <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
87 <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string> 86 <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 5e39bc7d9..dc527965c 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<resources> 2<resources>
3 <integer name="game_title_lines">2</integer> 3 <integer name="grid_columns">1</integer>
4 4
5 <!-- Default SWITCH landscape layout --> 5 <!-- Default SWITCH landscape layout -->
6 <integer name="SWITCH_BUTTON_A_X">760</integer> 6 <integer name="SWITCH_BUTTON_A_X">760</integer>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b163e6fc1..e51edf872 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -90,7 +90,6 @@
90 <string name="manage_save_data">Manage save data</string> 90 <string name="manage_save_data">Manage save data</string>
91 <string name="manage_save_data_description">Save data found. Please select an option below.</string> 91 <string name="manage_save_data_description">Save data found. Please select an option below.</string>
92 <string name="import_export_saves_description">Import or export save files</string> 92 <string name="import_export_saves_description">Import or export save files</string>
93 <string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
94 <string name="save_file_imported_success">Imported successfully</string> 93 <string name="save_file_imported_success">Imported successfully</string>
95 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> 94 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> 95 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -101,12 +100,13 @@
101 <string name="firmware_installing">Installing firmware</string> 100 <string name="firmware_installing">Installing firmware</string>
102 <string name="firmware_installed_success">Firmware installed successfully</string> 101 <string name="firmware_installed_success">Firmware installed successfully</string>
103 <string name="firmware_installed_failure">Firmware installation failed</string> 102 <string name="firmware_installed_failure">Firmware installation failed</string>
104 <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string> 103 <string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
105 <string name="share_log">Share debug logs</string> 104 <string name="share_log">Share debug logs</string>
106 <string name="share_log_description">Share yuzu\'s log file to debug issues</string> 105 <string name="share_log_description">Share yuzu\'s log file to debug issues</string>
107 <string name="share_log_missing">No log file found</string> 106 <string name="share_log_missing">No log file found</string>
108 <string name="install_game_content">Install game content</string> 107 <string name="install_game_content">Install game content</string>
109 <string name="install_game_content_description">Install game updates or DLC</string> 108 <string name="install_game_content_description">Install game updates or DLC</string>
109 <string name="installing_game_content">Installing content…</string>
110 <string name="install_game_content_failure">Error installing file(s) to NAND</string> 110 <string name="install_game_content_failure">Error installing file(s) to NAND</string>
111 <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string> 111 <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
112 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string> 112 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
@@ -118,6 +118,10 @@
118 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> 118 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
119 <string name="custom_driver_not_supported">Custom drivers not supported</string> 119 <string name="custom_driver_not_supported">Custom drivers not supported</string>
120 <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string> 120 <string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string>
121 <string name="manage_yuzu_data">Manage yuzu data</string>
122 <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
123 <string name="share_save_file">Share save file</string>
124 <string name="export_save_failed">Failed to export save</string>
121 125
122 <!-- About screen strings --> 126 <!-- About screen strings -->
123 <string name="gaia_is_not_real">Gaia isn\'t real</string> 127 <string name="gaia_is_not_real">Gaia isn\'t real</string>
@@ -128,6 +132,16 @@
128 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> 132 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
129 <string name="licenses_description">Projects that make yuzu for Android possible</string> 133 <string name="licenses_description">Projects that make yuzu for Android possible</string>
130 <string name="build">Build</string> 134 <string name="build">Build</string>
135 <string name="user_data">User data</string>
136 <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
137 <string name="exporting_user_data">Exporting user data…</string>
138 <string name="importing_user_data">Importing user data…</string>
139 <string name="import_user_data">Import user data</string>
140 <string name="invalid_yuzu_backup">Invalid yuzu backup</string>
141 <string name="user_data_export_success">User data exported successfully</string>
142 <string name="user_data_import_success">User data imported successfully</string>
143 <string name="user_data_export_cancelled">Export cancelled</string>
144 <string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
131 <string name="support_link">https://discord.gg/u77vRWY</string> 145 <string name="support_link">https://discord.gg/u77vRWY</string>
132 <string name="website_link">https://yuzu-emu.org/</string> 146 <string name="website_link">https://yuzu-emu.org/</string>
133 <string name="github_link">https://github.com/yuzu-emu</string> 147 <string name="github_link">https://github.com/yuzu-emu</string>
@@ -215,6 +229,11 @@
215 <string name="auto">Auto</string> 229 <string name="auto">Auto</string>
216 <string name="submit">Submit</string> 230 <string name="submit">Submit</string>
217 <string name="string_null">Null</string> 231 <string name="string_null">Null</string>
232 <string name="string_import">Import</string>
233 <string name="export">Export</string>
234 <string name="export_failed">Export failed</string>
235 <string name="import_failed">Import failed</string>
236 <string name="cancelling">Cancelling</string>
218 237
219 <!-- GPU driver installation --> 238 <!-- GPU driver installation -->
220 <string name="select_gpu_driver">Select GPU driver</string> 239 <string name="select_gpu_driver">Select GPU driver</string>
@@ -281,6 +300,7 @@
281 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> 300 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
282 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> 301 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
283 <string name="memory_formatted">%1$s %2$s</string> 302 <string name="memory_formatted">%1$s %2$s</string>
303 <string name="no_game_present">No bootable game present!</string>
284 304
285 <!-- Region Names --> 305 <!-- Region Names -->
286 <string name="region_japan">Japan</string> 306 <string name="region_japan">Japan</string>
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 67dfe0290..400988c5f 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -10,6 +10,13 @@ add_library(audio_core STATIC
10 adsp/apps/audio_renderer/command_buffer.h 10 adsp/apps/audio_renderer/command_buffer.h
11 adsp/apps/audio_renderer/command_list_processor.cpp 11 adsp/apps/audio_renderer/command_list_processor.cpp
12 adsp/apps/audio_renderer/command_list_processor.h 12 adsp/apps/audio_renderer/command_list_processor.h
13 adsp/apps/opus/opus_decoder.cpp
14 adsp/apps/opus/opus_decoder.h
15 adsp/apps/opus/opus_decode_object.cpp
16 adsp/apps/opus/opus_decode_object.h
17 adsp/apps/opus/opus_multistream_decode_object.cpp
18 adsp/apps/opus/opus_multistream_decode_object.h
19 adsp/apps/opus/shared_memory.h
13 audio_core.cpp 20 audio_core.cpp
14 audio_core.h 21 audio_core.h
15 audio_event.h 22 audio_event.h
@@ -35,6 +42,13 @@ add_library(audio_core STATIC
35 in/audio_in.h 42 in/audio_in.h
36 in/audio_in_system.cpp 43 in/audio_in_system.cpp
37 in/audio_in_system.h 44 in/audio_in_system.h
45 opus/hardware_opus.cpp
46 opus/hardware_opus.h
47 opus/decoder_manager.cpp
48 opus/decoder_manager.h
49 opus/decoder.cpp
50 opus/decoder.h
51 opus/parameters.h
38 out/audio_out.cpp 52 out/audio_out.cpp
39 out/audio_out.h 53 out/audio_out.h
40 out/audio_out_system.cpp 54 out/audio_out_system.cpp
@@ -214,7 +228,7 @@ else()
214 ) 228 )
215endif() 229endif()
216 230
217target_link_libraries(audio_core PUBLIC common core) 231target_link_libraries(audio_core PUBLIC common core Opus::opus)
218if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) 232if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
219 target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) 233 target_link_libraries(audio_core PRIVATE dynarmic::dynarmic)
220endif() 234endif()
diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp
index 0580990f5..6c53c98fd 100644
--- a/src/audio_core/adsp/adsp.cpp
+++ b/src/audio_core/adsp/adsp.cpp
@@ -7,12 +7,21 @@
7namespace AudioCore::ADSP { 7namespace AudioCore::ADSP {
8 8
9ADSP::ADSP(Core::System& system, Sink::Sink& sink) { 9ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
10 audio_renderer = 10 audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink);
11 std::make_unique<AudioRenderer::AudioRenderer>(system, system.ApplicationMemory(), sink); 11 opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system);
12 opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
13 if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) {
14 LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
15 return;
16 }
12} 17}
13 18
14AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { 19AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
15 return *audio_renderer.get(); 20 return *audio_renderer.get();
16} 21}
17 22
23OpusDecoder::OpusDecoder& ADSP::OpusDecoder() {
24 return *opus_decoder.get();
25}
26
18} // namespace AudioCore::ADSP 27} // namespace AudioCore::ADSP
diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h
index bd5bcc63b..a0c24a16a 100644
--- a/src/audio_core/adsp/adsp.h
+++ b/src/audio_core/adsp/adsp.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" 6#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
7#include "audio_core/adsp/apps/opus/opus_decoder.h"
7#include "common/common_types.h" 8#include "common/common_types.h"
8 9
9namespace Core { 10namespace Core {
@@ -40,10 +41,12 @@ public:
40 ~ADSP() = default; 41 ~ADSP() = default;
41 42
42 AudioRenderer::AudioRenderer& AudioRenderer(); 43 AudioRenderer::AudioRenderer& AudioRenderer();
44 OpusDecoder::OpusDecoder& OpusDecoder();
43 45
44private: 46private:
45 /// AudioRenderer app 47 /// AudioRenderer app
46 std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{}; 48 std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{};
49 std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{};
47}; 50};
48 51
49} // namespace ADSP 52} // namespace ADSP
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 2e549bc6f..972d5e45b 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -14,13 +14,12 @@
14#include "core/core.h" 14#include "core/core.h"
15#include "core/core_timing.h" 15#include "core/core_timing.h"
16 16
17MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); 17MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97));
18 18
19namespace AudioCore::ADSP::AudioRenderer { 19namespace AudioCore::ADSP::AudioRenderer {
20 20
21AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_, 21AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_)
22 Sink::Sink& sink_) 22 : system{system_}, sink{sink_} {}
23 : system{system_}, memory{memory_}, sink{sink_} {}
24 23
25AudioRenderer::~AudioRenderer() { 24AudioRenderer::~AudioRenderer() {
26 Stop(); 25 Stop();
@@ -33,8 +32,8 @@ void AudioRenderer::Start() {
33 32
34 main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); 33 main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });
35 34
36 mailbox.Send(Direction::DSP, {Message::InitializeOK, {}}); 35 mailbox.Send(Direction::DSP, Message::InitializeOK);
37 if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) { 36 if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
38 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " 37 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
39 "message response from ADSP!"); 38 "message response from ADSP!");
40 return; 39 return;
@@ -47,8 +46,8 @@ void AudioRenderer::Stop() {
47 return; 46 return;
48 } 47 }
49 48
50 mailbox.Send(Direction::DSP, {Message::Shutdown, {}}); 49 mailbox.Send(Direction::DSP, Message::Shutdown);
51 if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) { 50 if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
52 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " 51 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
53 "message response from ADSP!"); 52 "message response from ADSP!");
54 } 53 }
@@ -67,25 +66,25 @@ void AudioRenderer::Stop() {
67 66
68void AudioRenderer::Signal() { 67void AudioRenderer::Signal() {
69 signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); 68 signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
70 Send(Direction::DSP, {Message::Render, {}}); 69 Send(Direction::DSP, Message::Render);
71} 70}
72 71
73void AudioRenderer::Wait() { 72void AudioRenderer::Wait() {
74 auto received = Receive(Direction::Host); 73 auto msg = Receive(Direction::Host);
75 if (received.msg != Message::RenderResponse) { 74 if (msg != Message::RenderResponse) {
76 LOG_ERROR(Service_Audio, 75 LOG_ERROR(Service_Audio,
77 "Did not receive the expected render response from the AudioRenderer! Expected " 76 "Did not receive the expected render response from the AudioRenderer! Expected "
78 "{}, got {}", 77 "{}, got {}",
79 Message::RenderResponse, received.msg); 78 Message::RenderResponse, msg);
80 } 79 }
81} 80}
82 81
83void AudioRenderer::Send(Direction dir, MailboxMessage message) { 82void AudioRenderer::Send(Direction dir, u32 message) {
84 mailbox.Send(dir, std::move(message)); 83 mailbox.Send(dir, std::move(message));
85} 84}
86 85
87MailboxMessage AudioRenderer::Receive(Direction dir, bool block) { 86u32 AudioRenderer::Receive(Direction dir) {
88 return mailbox.Receive(dir, block); 87 return mailbox.Receive(dir);
89} 88}
90 89
91void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, 90void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
@@ -120,7 +119,7 @@ void AudioRenderer::CreateSinkStreams() {
120} 119}
121 120
122void AudioRenderer::Main(std::stop_token stop_token) { 121void AudioRenderer::Main(std::stop_token stop_token) {
123 static constexpr char name[]{"AudioRenderer"}; 122 static constexpr char name[]{"DSP_AudioRenderer_Main"};
124 MicroProfileOnThreadCreate(name); 123 MicroProfileOnThreadCreate(name);
125 Common::SetCurrentThreadName(name); 124 Common::SetCurrentThreadName(name);
126 Common::SetCurrentThreadPriority(Common::ThreadPriority::High); 125 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
@@ -128,28 +127,28 @@ void AudioRenderer::Main(std::stop_token stop_token) {
128 // TODO: Create buffer map/unmap thread + mailbox 127 // TODO: Create buffer map/unmap thread + mailbox
129 // TODO: Create gMix devices, initialize them here 128 // TODO: Create gMix devices, initialize them here
130 129
131 if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) { 130 if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) {
132 LOG_ERROR(Service_Audio, 131 LOG_ERROR(Service_Audio,
133 "ADSP Audio Renderer -- Failed to receive initialize message from host!"); 132 "ADSP Audio Renderer -- Failed to receive initialize message from host!");
134 return; 133 return;
135 } 134 }
136 135
137 mailbox.Send(Direction::Host, {Message::InitializeOK, {}}); 136 mailbox.Send(Direction::Host, Message::InitializeOK);
138 137
139 // 0.12 seconds (2,304,000 / 19,200,000) 138 // 0.12 seconds (2,304,000 / 19,200,000)
140 constexpr u64 max_process_time{2'304'000ULL}; 139 constexpr u64 max_process_time{2'304'000ULL};
141 140
142 while (!stop_token.stop_requested()) { 141 while (!stop_token.stop_requested()) {
143 auto received{mailbox.Receive(Direction::DSP)}; 142 auto msg{mailbox.Receive(Direction::DSP)};
144 switch (received.msg) { 143 switch (msg) {
145 case Message::Shutdown: 144 case Message::Shutdown:
146 mailbox.Send(Direction::Host, {Message::Shutdown, {}}); 145 mailbox.Send(Direction::Host, Message::Shutdown);
147 return; 146 return;
148 147
149 case Message::Render: { 148 case Message::Render: {
150 if (system.IsShuttingDown()) [[unlikely]] { 149 if (system.IsShuttingDown()) [[unlikely]] {
151 std::this_thread::sleep_for(std::chrono::milliseconds(5)); 150 std::this_thread::sleep_for(std::chrono::milliseconds(5));
152 mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); 151 mailbox.Send(Direction::Host, Message::RenderResponse);
153 continue; 152 continue;
154 } 153 }
155 std::array<bool, MaxRendererSessions> buffers_reset{}; 154 std::array<bool, MaxRendererSessions> buffers_reset{};
@@ -205,13 +204,12 @@ void AudioRenderer::Main(std::stop_token stop_token) {
205 } 204 }
206 } 205 }
207 206
208 mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); 207 mailbox.Send(Direction::Host, Message::RenderResponse);
209 } break; 208 } break;
210 209
211 default: 210 default:
212 LOG_WARNING(Service_Audio, 211 LOG_WARNING(Service_Audio,
213 "ADSP AudioRenderer received an invalid message, msg={:02X}!", 212 "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg);
214 received.msg);
215 break; 213 break;
216 } 214 }
217 } 215 }
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 3f5b7dca2..85874d88a 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -17,13 +17,6 @@
17 17
18namespace Core { 18namespace Core {
19class System; 19class System;
20namespace Timing {
21struct EventType;
22}
23namespace Memory {
24class Memory;
25}
26class System;
27} // namespace Core 20} // namespace Core
28 21
29namespace AudioCore { 22namespace AudioCore {
@@ -34,19 +27,19 @@ class Sink;
34namespace ADSP::AudioRenderer { 27namespace ADSP::AudioRenderer {
35 28
36enum Message : u32 { 29enum Message : u32 {
37 Invalid = 0x00, 30 Invalid = 0,
38 MapUnmap_Map = 0x01, 31 MapUnmap_Map = 1,
39 MapUnmap_MapResponse = 0x02, 32 MapUnmap_MapResponse = 2,
40 MapUnmap_Unmap = 0x03, 33 MapUnmap_Unmap = 3,
41 MapUnmap_UnmapResponse = 0x04, 34 MapUnmap_UnmapResponse = 4,
42 MapUnmap_InvalidateCache = 0x05, 35 MapUnmap_InvalidateCache = 5,
43 MapUnmap_InvalidateCacheResponse = 0x06, 36 MapUnmap_InvalidateCacheResponse = 6,
44 MapUnmap_Shutdown = 0x07, 37 MapUnmap_Shutdown = 7,
45 MapUnmap_ShutdownResponse = 0x08, 38 MapUnmap_ShutdownResponse = 8,
46 InitializeOK = 0x16, 39 InitializeOK = 22,
47 RenderResponse = 0x20, 40 RenderResponse = 32,
48 Render = 0x2A, 41 Render = 42,
49 Shutdown = 0x34, 42 Shutdown = 52,
50}; 43};
51 44
52/** 45/**
@@ -54,7 +47,7 @@ enum Message : u32 {
54 */ 47 */
55class AudioRenderer { 48class AudioRenderer {
56public: 49public:
57 explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink); 50 explicit AudioRenderer(Core::System& system, Sink::Sink& sink);
58 ~AudioRenderer(); 51 ~AudioRenderer();
59 52
60 /** 53 /**
@@ -72,8 +65,8 @@ public:
72 void Signal(); 65 void Signal();
73 void Wait(); 66 void Wait();
74 67
75 void Send(Direction dir, MailboxMessage message); 68 void Send(Direction dir, u32 message);
76 MailboxMessage Receive(Direction dir, bool block = true); 69 u32 Receive(Direction dir);
77 70
78 void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, 71 void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
79 u64 applet_resource_user_id, bool reset) noexcept; 72 u64 applet_resource_user_id, bool reset) noexcept;
@@ -94,9 +87,7 @@ private:
94 87
95 /// Core system 88 /// Core system
96 Core::System& system; 89 Core::System& system;
97 /// Memory 90 /// The output sink the AudioRenderer will send samples to
98 Core::Memory::Memory& memory;
99 /// The output sink the AudioRenderer will use
100 Sink::Sink& sink; 91 Sink::Sink& sink;
101 /// The active mailbox 92 /// The active mailbox
102 Mailbox mailbox; 93 Mailbox mailbox;
@@ -104,11 +95,13 @@ private:
104 std::jthread main_thread{}; 95 std::jthread main_thread{};
105 /// The current state 96 /// The current state
106 std::atomic<bool> running{}; 97 std::atomic<bool> running{};
98 /// Shared memory of input command buffers, set by host, read by DSP
107 std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; 99 std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
108 /// The command lists to process 100 /// The command lists to process
109 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; 101 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
110 /// The streams which will receive the processed samples 102 /// The streams which will receive the processed samples
111 std::array<Sink::SinkStream*, MaxRendererSessions> streams{}; 103 std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
104 /// CPU Tick when the DSP was signalled to process, uses time rather than tick
112 u64 signalled_tick{0}; 105 u64 signalled_tick{0};
113}; 106};
114 107
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
new file mode 100644
index 000000000..2c16d3769
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
@@ -0,0 +1,107 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_decode_object.h"
5#include "common/assert.h"
6
7namespace AudioCore::ADSP::OpusDecoder {
8namespace {
9bool IsValidChannelCount(u32 channel_count) {
10 return channel_count == 1 || channel_count == 2;
11}
12} // namespace
13
14u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
15 if (!IsValidChannelCount(channel_count)) {
16 return 0;
17 }
18 return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
19}
20
21OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
22 auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
23 auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
24
25 if (new_decoder->magic == DecodeObjectMagic) {
26 if (!new_decoder->initialized ||
27 (new_decoder->initialized && new_decoder->self == comparison)) {
28 new_decoder->state_valid = true;
29 }
30 } else {
31 new_decoder->initialized = false;
32 new_decoder->state_valid = true;
33 }
34 return *new_decoder;
35}
36
37s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
38 if (!state_valid) {
39 return OPUS_INVALID_STATE;
40 }
41
42 if (initialized) {
43 return OPUS_OK;
44 }
45
46 // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
47 // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
48 // provided.
49 // We could use _create and have libopus allocate it for us, but then we have to separately
50 // track which decoder is being used between this and multistream in order to call the correct
51 // destroy from the host side.
52 // This is a bit cringe, but is safe as these objects are only ever initialized inside the given
53 // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
54 decoder = (LibOpusDecoder*)(this + 1);
55 s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
56 if (ret == OPUS_OK) {
57 magic = DecodeObjectMagic;
58 initialized = true;
59 state_valid = true;
60 self = this;
61 final_range = 0;
62 }
63 return ret;
64}
65
66s32 OpusDecodeObject::Shutdown() {
67 if (!state_valid) {
68 return OPUS_INVALID_STATE;
69 }
70
71 if (initialized) {
72 magic = 0x0;
73 initialized = false;
74 state_valid = false;
75 self = nullptr;
76 final_range = 0;
77 decoder = nullptr;
78 }
79 return OPUS_OK;
80}
81
82s32 OpusDecodeObject::ResetDecoder() {
83 return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
84}
85
86s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
87 u64 input_data, u64 input_data_size) {
88 ASSERT(initialized);
89 out_sample_count = 0;
90
91 if (!state_valid) {
92 return OPUS_INVALID_STATE;
93 }
94
95 auto ret_code_or_samples = opus_decode(
96 decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
97 reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
98
99 if (ret_code_or_samples < OPUS_OK) {
100 return ret_code_or_samples;
101 }
102
103 out_sample_count = ret_code_or_samples;
104 return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
105}
106
107} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h
new file mode 100644
index 000000000..6425f987c
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <opus.h>
7
8#include "common/common_types.h"
9
10namespace AudioCore::ADSP::OpusDecoder {
11using LibOpusDecoder = ::OpusDecoder;
12static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
13
14class OpusDecodeObject {
15public:
16 static u32 GetWorkBufferSize(u32 channel_count);
17 static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
18
19 s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
20 s32 Shutdown();
21 s32 ResetDecoder();
22 s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
23 u64 input_data_size);
24 u32 GetFinalRange() const noexcept {
25 return final_range;
26 }
27
28private:
29 u32 magic;
30 bool initialized;
31 bool state_valid;
32 OpusDecodeObject* self;
33 u32 final_range;
34 LibOpusDecoder* decoder;
35};
36static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
37
38} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
new file mode 100644
index 000000000..2084de128
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
@@ -0,0 +1,269 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/adsp/apps/opus/opus_decode_object.h"
8#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
9#include "audio_core/adsp/apps/opus/shared_memory.h"
10#include "audio_core/audio_core.h"
11#include "audio_core/common/common.h"
12#include "common/logging/log.h"
13#include "common/microprofile.h"
14#include "common/thread.h"
15#include "core/core.h"
16#include "core/core_timing.h"
17
18MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97));
19
20namespace AudioCore::ADSP::OpusDecoder {
21
22namespace {
23constexpr size_t OpusStreamCountMax = 255;
24
25bool IsValidChannelCount(u32 channel_count) {
26 return channel_count == 1 || channel_count == 2;
27}
28
29bool IsValidMultiStreamChannelCount(u32 channel_count) {
30 return channel_count <= OpusStreamCountMax;
31}
32
33bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
34 return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
35 sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
36}
37} // namespace
38
39OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
40 init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
41}
42
43OpusDecoder::~OpusDecoder() {
44 if (!running) {
45 init_thread.request_stop();
46 return;
47 }
48
49 // Shutdown the thread
50 Send(Direction::DSP, Message::Shutdown);
51 auto msg = Receive(Direction::Host);
52 ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
53 Message::ShutdownOK, msg);
54 main_thread.request_stop();
55 main_thread.join();
56 running = false;
57}
58
59void OpusDecoder::Send(Direction dir, u32 message) {
60 mailbox.Send(dir, std::move(message));
61}
62
63u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
64 return mailbox.Receive(dir, stop_token);
65}
66
67void OpusDecoder::Init(std::stop_token stop_token) {
68 Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
69
70 if (Receive(Direction::DSP, stop_token) != Message::Start) {
71 LOG_ERROR(Service_Audio,
72 "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
73 return;
74 }
75 main_thread = std::jthread([this](std::stop_token st) { Main(st); });
76 running = true;
77 Send(Direction::Host, Message::StartOK);
78}
79
80void OpusDecoder::Main(std::stop_token stop_token) {
81 Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
82
83 while (!stop_token.stop_requested()) {
84 auto msg = Receive(Direction::DSP, stop_token);
85 switch (msg) {
86 case Shutdown:
87 Send(Direction::Host, Message::ShutdownOK);
88 return;
89
90 case GetWorkBufferSize: {
91 auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
92
93 ASSERT(IsValidChannelCount(channel_count));
94
95 shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
96 Send(Direction::Host, Message::GetWorkBufferSizeOK);
97 } break;
98
99 case InitializeDecodeObject: {
100 auto buffer = shared_memory->host_send_data[0];
101 auto buffer_size = shared_memory->host_send_data[1];
102 auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
103 auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
104
105 ASSERT(sample_rate >= 0);
106 ASSERT(IsValidChannelCount(channel_count));
107 ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
108
109 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
110 shared_memory->dsp_return_data[0] =
111 decoder_object.InitializeDecoder(sample_rate, channel_count);
112
113 Send(Direction::Host, Message::InitializeDecodeObjectOK);
114 } break;
115
116 case ShutdownDecodeObject: {
117 auto buffer = shared_memory->host_send_data[0];
118 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
119
120 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
121 shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
122
123 Send(Direction::Host, Message::ShutdownDecodeObjectOK);
124 } break;
125
126 case DecodeInterleaved: {
127 auto start_time = system.CoreTiming().GetGlobalTimeUs();
128
129 auto buffer = shared_memory->host_send_data[0];
130 auto input_data = shared_memory->host_send_data[1];
131 auto input_data_size = shared_memory->host_send_data[2];
132 auto output_data = shared_memory->host_send_data[3];
133 auto output_data_size = shared_memory->host_send_data[4];
134 auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
135 auto reset_requested = shared_memory->host_send_data[6];
136
137 u32 decoded_samples{0};
138
139 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
140 s32 error_code{OPUS_OK};
141 if (reset_requested) {
142 error_code = decoder_object.ResetDecoder();
143 }
144
145 if (error_code == OPUS_OK) {
146 error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
147 input_data, input_data_size);
148 }
149
150 if (error_code == OPUS_OK) {
151 if (final_range && decoder_object.GetFinalRange() != final_range) {
152 error_code = OPUS_INVALID_PACKET;
153 }
154 }
155
156 auto end_time = system.CoreTiming().GetGlobalTimeUs();
157 shared_memory->dsp_return_data[0] = error_code;
158 shared_memory->dsp_return_data[1] = decoded_samples;
159 shared_memory->dsp_return_data[2] = (end_time - start_time).count();
160
161 Send(Direction::Host, Message::DecodeInterleavedOK);
162 } break;
163
164 case MapMemory: {
165 [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
166 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
167 Send(Direction::Host, Message::MapMemoryOK);
168 } break;
169
170 case UnmapMemory: {
171 [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
172 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
173 Send(Direction::Host, Message::UnmapMemoryOK);
174 } break;
175
176 case GetWorkBufferSizeForMultiStream: {
177 auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
178 auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
179
180 ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
181
182 shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
183 total_stream_count, stereo_stream_count);
184 Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
185 } break;
186
187 case InitializeMultiStreamDecodeObject: {
188 auto buffer = shared_memory->host_send_data[0];
189 auto buffer_size = shared_memory->host_send_data[1];
190 auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
191 auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
192 auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
193 auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
194 // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
195 // mappings, but [6] is never set, and there is not enough room in the argument data for
196 // more than 40 channels, when 255 are possible.
197 // It also means the mapping values are undefined, though likely always 0,
198 // and the mappings given by the game are ignored. The mappings are copied to this
199 // dedicated buffer host side, so let's do as intended.
200 auto mappings = shared_memory->channel_mapping.data();
201
202 ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
203 ASSERT(sample_rate >= 0);
204 ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
205 total_stream_count, stereo_stream_count));
206
207 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
208 shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
209 sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
210
211 Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
212 } break;
213
214 case ShutdownMultiStreamDecodeObject: {
215 auto buffer = shared_memory->host_send_data[0];
216 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
217
218 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
219 shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
220
221 Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
222 } break;
223
224 case DecodeInterleavedForMultiStream: {
225 auto start_time = system.CoreTiming().GetGlobalTimeUs();
226
227 auto buffer = shared_memory->host_send_data[0];
228 auto input_data = shared_memory->host_send_data[1];
229 auto input_data_size = shared_memory->host_send_data[2];
230 auto output_data = shared_memory->host_send_data[3];
231 auto output_data_size = shared_memory->host_send_data[4];
232 auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
233 auto reset_requested = shared_memory->host_send_data[6];
234
235 u32 decoded_samples{0};
236
237 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
238 s32 error_code{OPUS_OK};
239 if (reset_requested) {
240 error_code = decoder_object.ResetDecoder();
241 }
242
243 if (error_code == OPUS_OK) {
244 error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
245 input_data, input_data_size);
246 }
247
248 if (error_code == OPUS_OK) {
249 if (final_range && decoder_object.GetFinalRange() != final_range) {
250 error_code = OPUS_INVALID_PACKET;
251 }
252 }
253
254 auto end_time = system.CoreTiming().GetGlobalTimeUs();
255 shared_memory->dsp_return_data[0] = error_code;
256 shared_memory->dsp_return_data[1] = decoded_samples;
257 shared_memory->dsp_return_data[2] = (end_time - start_time).count();
258
259 Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
260 } break;
261
262 default:
263 LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
264 continue;
265 }
266 }
267}
268
269} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h
new file mode 100644
index 000000000..fcb89bb40
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.h
@@ -0,0 +1,92 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <thread>
8
9#include "audio_core/adsp/apps/opus/shared_memory.h"
10#include "audio_core/adsp/mailbox.h"
11#include "common/common_types.h"
12
13namespace Core {
14class System;
15} // namespace Core
16
17namespace AudioCore::ADSP::OpusDecoder {
18
19enum Message : u32 {
20 Invalid = 0,
21 Start = 1,
22 Shutdown = 2,
23 StartOK = 11,
24 ShutdownOK = 12,
25 GetWorkBufferSize = 21,
26 InitializeDecodeObject = 22,
27 ShutdownDecodeObject = 23,
28 DecodeInterleaved = 24,
29 MapMemory = 25,
30 UnmapMemory = 26,
31 GetWorkBufferSizeForMultiStream = 27,
32 InitializeMultiStreamDecodeObject = 28,
33 ShutdownMultiStreamDecodeObject = 29,
34 DecodeInterleavedForMultiStream = 30,
35
36 GetWorkBufferSizeOK = 41,
37 InitializeDecodeObjectOK = 42,
38 ShutdownDecodeObjectOK = 43,
39 DecodeInterleavedOK = 44,
40 MapMemoryOK = 45,
41 UnmapMemoryOK = 46,
42 GetWorkBufferSizeForMultiStreamOK = 47,
43 InitializeMultiStreamDecodeObjectOK = 48,
44 ShutdownMultiStreamDecodeObjectOK = 49,
45 DecodeInterleavedForMultiStreamOK = 50,
46};
47
48/**
49 * The AudioRenderer application running on the ADSP.
50 */
51class OpusDecoder {
52public:
53 explicit OpusDecoder(Core::System& system);
54 ~OpusDecoder();
55
56 bool IsRunning() const noexcept {
57 return running;
58 }
59
60 void Send(Direction dir, u32 message);
61 u32 Receive(Direction dir, std::stop_token stop_token = {});
62
63 void SetSharedMemory(SharedMemory& shared_memory_) {
64 shared_memory = &shared_memory_;
65 }
66
67private:
68 /**
69 * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
70 */
71 void Init(std::stop_token stop_token);
72 /**
73 * Main OpusDecoder thread, responsible for processing the incoming Opus packets.
74 */
75 void Main(std::stop_token stop_token);
76
77 /// Core system
78 Core::System& system;
79 /// Mailbox to communicate messages with the host, drives the main thread
80 Mailbox mailbox;
81 /// Init thread
82 std::jthread init_thread{};
83 /// Main thread
84 std::jthread main_thread{};
85 /// The current state
86 bool running{};
87 /// Structure shared with the host, input data set by the host before sending a mailbox message,
88 /// and the responses are written back by the OpusDecoder.
89 SharedMemory* shared_memory{};
90};
91
92} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
new file mode 100644
index 000000000..f6d362e68
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
@@ -0,0 +1,111 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
5#include "common/assert.h"
6
7namespace AudioCore::ADSP::OpusDecoder {
8
9namespace {
10bool IsValidChannelCount(u32 channel_count) {
11 return channel_count == 1 || channel_count == 2;
12}
13
14bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
15 return total_stream_count > 0 && stereo_stream_count > 0 &&
16 stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
17}
18} // namespace
19
20u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
21 u32 stereo_stream_count) {
22 if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
23 return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
24 opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
25 }
26 return 0;
27}
28
29OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
30 auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
31 auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
32
33 if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
34 if (!new_decoder->initialized ||
35 (new_decoder->initialized && new_decoder->self == comparison)) {
36 new_decoder->state_valid = true;
37 }
38 } else {
39 new_decoder->initialized = false;
40 new_decoder->state_valid = true;
41 }
42 return *new_decoder;
43}
44
45s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
46 u32 channel_count, u32 stereo_stream_count,
47 u8* mappings) {
48 if (!state_valid) {
49 return OPUS_INVALID_STATE;
50 }
51
52 if (initialized) {
53 return OPUS_OK;
54 }
55
56 // See OpusDecodeObject::InitializeDecoder for an explanation of this
57 decoder = (LibOpusMSDecoder*)(this + 1);
58 s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
59 stereo_stream_count, mappings);
60 if (ret == OPUS_OK) {
61 magic = DecodeMultiStreamObjectMagic;
62 initialized = true;
63 state_valid = true;
64 self = this;
65 final_range = 0;
66 }
67 return ret;
68}
69
70s32 OpusMultiStreamDecodeObject::Shutdown() {
71 if (!state_valid) {
72 return OPUS_INVALID_STATE;
73 }
74
75 if (initialized) {
76 magic = 0x0;
77 initialized = false;
78 state_valid = false;
79 self = nullptr;
80 final_range = 0;
81 decoder = nullptr;
82 }
83 return OPUS_OK;
84}
85
86s32 OpusMultiStreamDecodeObject::ResetDecoder() {
87 return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
88}
89
90s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
91 u64 output_data_size, u64 input_data, u64 input_data_size) {
92 ASSERT(initialized);
93 out_sample_count = 0;
94
95 if (!state_valid) {
96 return OPUS_INVALID_STATE;
97 }
98
99 auto ret_code_or_samples = opus_multistream_decode(
100 decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
101 reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
102
103 if (ret_code_or_samples < OPUS_OK) {
104 return ret_code_or_samples;
105 }
106
107 out_sample_count = ret_code_or_samples;
108 return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
109}
110
111} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h
new file mode 100644
index 000000000..93558ded5
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <opus_multistream.h>
7
8#include "common/common_types.h"
9
10namespace AudioCore::ADSP::OpusDecoder {
11using LibOpusMSDecoder = ::OpusMSDecoder;
12static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
13
14class OpusMultiStreamDecodeObject {
15public:
16 static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
17 static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
18
19 s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
20 u32 stereo_stream_count, u8* mappings);
21 s32 Shutdown();
22 s32 ResetDecoder();
23 s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
24 u64 input_data_size);
25 u32 GetFinalRange() const noexcept {
26 return final_range;
27 }
28
29private:
30 u32 magic;
31 bool initialized;
32 bool state_valid;
33 OpusMultiStreamDecodeObject* self;
34 u32 final_range;
35 LibOpusMSDecoder* decoder;
36};
37static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
38
39} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h
new file mode 100644
index 000000000..c696731ed
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/shared_memory.h
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace AudioCore::ADSP::OpusDecoder {
10
11struct SharedMemory {
12 std::array<u8, 0x100> channel_mapping{};
13 std::array<u64, 16> host_send_data{};
14 std::array<u64, 16> dsp_return_data{};
15};
16
17} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h
index c31b73717..1dd40ebfa 100644
--- a/src/audio_core/adsp/mailbox.h
+++ b/src/audio_core/adsp/mailbox.h
@@ -3,6 +3,8 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <span>
7
6#include "common/bounded_threadsafe_queue.h" 8#include "common/bounded_threadsafe_queue.h"
7#include "common/common_types.h" 9#include "common/common_types.h"
8 10
@@ -19,11 +21,6 @@ enum class Direction : u32 {
19 DSP, 21 DSP,
20}; 22};
21 23
22struct MailboxMessage {
23 u32 msg;
24 std::span<u8> data;
25};
26
27class Mailbox { 24class Mailbox {
28public: 25public:
29 void Initialize(AppMailboxId id_) { 26 void Initialize(AppMailboxId id_) {
@@ -35,25 +32,19 @@ public:
35 return id; 32 return id;
36 } 33 }
37 34
38 void Send(Direction dir, MailboxMessage&& message) { 35 void Send(Direction dir, u32 message) {
39 auto& queue = dir == Direction::Host ? host_queue : adsp_queue; 36 auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
40 queue.EmplaceWait(std::move(message)); 37 queue.EmplaceWait(message);
41 } 38 }
42 39
43 MailboxMessage Receive(Direction dir, bool block = true) { 40 u32 Receive(Direction dir, std::stop_token stop_token = {}) {
44 auto& queue = dir == Direction::Host ? host_queue : adsp_queue; 41 auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
45 MailboxMessage t; 42 return queue.PopWait(stop_token);
46 if (block) {
47 queue.PopWait(t);
48 } else {
49 queue.TryPop(t);
50 }
51 return t;
52 } 43 }
53 44
54 void Reset() { 45 void Reset() {
55 id = AppMailboxId::Invalid; 46 id = AppMailboxId::Invalid;
56 MailboxMessage t; 47 u32 t{};
57 while (host_queue.TryPop(t)) { 48 while (host_queue.TryPop(t)) {
58 } 49 }
59 while (adsp_queue.TryPop(t)) { 50 while (adsp_queue.TryPop(t)) {
@@ -62,8 +53,8 @@ public:
62 53
63private: 54private:
64 AppMailboxId id{0}; 55 AppMailboxId id{0};
65 Common::SPSCQueue<MailboxMessage> host_queue; 56 Common::SPSCQueue<u32> host_queue;
66 Common::SPSCQueue<MailboxMessage> adsp_queue; 57 Common::SPSCQueue<u32> adsp_queue;
67}; 58};
68 59
69} // namespace AudioCore::ADSP 60} // namespace AudioCore::ADSP
diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp
new file mode 100644
index 000000000..5b23fce14
--- /dev/null
+++ b/src/audio_core/opus/decoder.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/opus/decoder.h"
5#include "audio_core/opus/hardware_opus.h"
6#include "audio_core/opus/parameters.h"
7#include "common/alignment.h"
8#include "common/swap.h"
9#include "core/core.h"
10
11namespace AudioCore::OpusDecoder {
12using namespace Service::Audio;
13namespace {
14OpusPacketHeader ReverseHeader(OpusPacketHeader header) {
15 OpusPacketHeader out;
16 out.size = Common::swap32(header.size);
17 out.final_range = Common::swap32(header.final_range);
18 return out;
19}
20} // namespace
21
22OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
23 : system{system_}, hardware_opus{hardware_opus_} {}
24
25OpusDecoder::~OpusDecoder() {
26 if (decode_object_initialized) {
27 hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size);
28 }
29}
30
31Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
32 u64 transfer_memory_size) {
33 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
34 shared_buffer_size = transfer_memory_size;
35 shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
36 shared_memory_mapped = true;
37
38 buffer_size =
39 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
40
41 out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
42 size_t in_data_size{0x600u};
43 in_data = {out_data.data() - in_data_size, in_data_size};
44
45 ON_RESULT_FAILURE {
46 if (shared_memory_mapped) {
47 shared_memory_mapped = false;
48 ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
49 }
50 };
51
52 R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count,
53 shared_buffer.get(), shared_buffer_size));
54
55 sample_rate = params.sample_rate;
56 channel_count = params.channel_count;
57 use_large_frame_size = params.use_large_frame_size;
58 decode_object_initialized = true;
59 R_SUCCEED();
60}
61
62Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params,
63 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
64 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
65 shared_buffer_size = transfer_memory_size;
66 shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
67 shared_memory_mapped = true;
68
69 buffer_size =
70 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
71
72 out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
73 size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)};
74 in_data = {out_data.data() - in_data_size, in_data_size};
75
76 ON_RESULT_FAILURE {
77 if (shared_memory_mapped) {
78 shared_memory_mapped = false;
79 ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
80 }
81 };
82
83 R_TRY(hardware_opus.InitializeMultiStreamDecodeObject(
84 params.sample_rate, params.channel_count, params.total_stream_count,
85 params.stereo_stream_count, params.mappings.data(), shared_buffer.get(),
86 shared_buffer_size));
87
88 sample_rate = params.sample_rate;
89 channel_count = params.channel_count;
90 total_stream_count = params.total_stream_count;
91 stereo_stream_count = params.stereo_stream_count;
92 use_large_frame_size = params.use_large_frame_size;
93 decode_object_initialized = true;
94 R_SUCCEED();
95}
96
97Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken,
98 u32* out_sample_count, std::span<const u8> input_data,
99 std::span<u8> output_data, bool reset) {
100 u32 out_samples;
101 u64 time_taken{};
102
103 R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
104
105 auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
106 OpusPacketHeader header{ReverseHeader(*header_p)};
107
108 R_UNLESS(in_data.size_bytes() >= header.size &&
109 header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
110 ResultBufferTooSmall);
111
112 if (!shared_memory_mapped) {
113 R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
114 shared_memory_mapped = true;
115 }
116
117 std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
118
119 R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(),
120 channel_count, in_data.data(), header.size,
121 shared_buffer.get(), time_taken, reset));
122
123 std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
124
125 *out_data_size = header.size + sizeof(OpusPacketHeader);
126 *out_sample_count = out_samples;
127 if (out_time_taken) {
128 *out_time_taken = time_taken / 1000;
129 }
130 R_SUCCEED();
131}
132
133Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) {
134 R_SUCCEED_IF(shared_memory_mapped);
135 shared_memory_mapped = true;
136 R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
137}
138
139Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
140 u32* out_sample_count,
141 std::span<const u8> input_data,
142 std::span<u8> output_data, bool reset) {
143 u32 out_samples;
144 u64 time_taken{};
145
146 R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
147
148 auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
149 OpusPacketHeader header{ReverseHeader(*header_p)};
150
151 LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}",
152 header.size, input_data.size_bytes(), in_data.size_bytes());
153
154 R_UNLESS(in_data.size_bytes() >= header.size &&
155 header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
156 ResultBufferTooSmall);
157
158 if (!shared_memory_mapped) {
159 R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
160 shared_memory_mapped = true;
161 }
162
163 std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
164
165 R_TRY(hardware_opus.DecodeInterleavedForMultiStream(
166 out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(),
167 header.size, shared_buffer.get(), time_taken, reset));
168
169 std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
170
171 *out_data_size = header.size + sizeof(OpusPacketHeader);
172 *out_sample_count = out_samples;
173 if (out_time_taken) {
174 *out_time_taken = time_taken / 1000;
175 }
176 R_SUCCEED();
177}
178
179} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h
new file mode 100644
index 000000000..d08d8a4a4
--- /dev/null
+++ b/src/audio_core/opus/decoder.h
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/opus/parameters.h"
9#include "common/common_types.h"
10#include "core/hle/kernel/k_transfer_memory.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace Core {
14class System;
15}
16
17namespace AudioCore::OpusDecoder {
18class HardwareOpus;
19
20class OpusDecoder {
21public:
22 explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_);
23 ~OpusDecoder();
24
25 Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
26 u64 transfer_memory_size);
27 Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
28 u64 transfer_memory_size);
29 Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count,
30 std::span<const u8> input_data, std::span<u8> output_data, bool reset);
31 Result SetContext([[maybe_unused]] std::span<const u8> context);
32 Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
33 u32* out_sample_count, std::span<const u8> input_data,
34 std::span<u8> output_data, bool reset);
35
36private:
37 Core::System& system;
38 HardwareOpus& hardware_opus;
39 std::unique_ptr<u8[]> shared_buffer{};
40 u64 shared_buffer_size;
41 std::span<u8> in_data{};
42 std::span<u8> out_data{};
43 u64 buffer_size{};
44 s32 sample_rate{};
45 s32 channel_count{};
46 bool use_large_frame_size{false};
47 s32 total_stream_count{};
48 s32 stereo_stream_count{};
49 bool shared_memory_mapped{false};
50 bool decode_object_initialized{false};
51};
52
53} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp
new file mode 100644
index 000000000..4a5382973
--- /dev/null
+++ b/src/audio_core/opus/decoder_manager.cpp
@@ -0,0 +1,102 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_decoder.h"
5#include "audio_core/opus/decoder_manager.h"
6#include "common/alignment.h"
7#include "core/core.h"
8
9namespace AudioCore::OpusDecoder {
10using namespace Service::Audio;
11
12namespace {
13bool IsValidChannelCount(u32 channel_count) {
14 return channel_count == 1 || channel_count == 2;
15}
16
17bool IsValidMultiStreamChannelCount(u32 channel_count) {
18 return channel_count > 0 && channel_count <= OpusStreamCountMax;
19}
20
21bool IsValidSampleRate(u32 sample_rate) {
22 return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 ||
23 sample_rate == 24'000 || sample_rate == 48'000;
24}
25
26bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) {
27 return total_stream_count > 0 && stereo_stream_count > 0 &&
28 stereo_stream_count <= total_stream_count &&
29 total_stream_count + stereo_stream_count <= channel_count;
30}
31
32} // namespace
33
34OpusDecoderManager::OpusDecoderManager(Core::System& system_)
35 : system{system_}, hardware_opus{system} {
36 for (u32 i = 0; i < MaxChannels; i++) {
37 required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i);
38 }
39}
40
41Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) {
42 OpusParametersEx ex{
43 .sample_rate = params.sample_rate,
44 .channel_count = params.channel_count,
45 .use_large_frame_size = false,
46 };
47 R_RETURN(GetWorkBufferSizeExEx(ex, out_size));
48}
49
50Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) {
51 R_RETURN(GetWorkBufferSizeExEx(params, out_size));
52}
53
54Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) {
55 R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
56 R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
57
58 auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]};
59 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
60 work_buffer_size +=
61 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
62 out_size = work_buffer_size + 0x600;
63 R_SUCCEED();
64}
65
66Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params,
67 u64& out_size) {
68 OpusMultiStreamParametersEx ex{
69 .sample_rate = params.sample_rate,
70 .channel_count = params.channel_count,
71 .total_stream_count = params.total_stream_count,
72 .stereo_stream_count = params.stereo_stream_count,
73 .use_large_frame_size = false,
74 .mappings = {},
75 };
76 R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size));
77}
78
79Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params,
80 u64& out_size) {
81 R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size));
82}
83
84Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params,
85 u64& out_size) {
86 R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
87 R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
88 R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count,
89 params.stereo_stream_count),
90 ResultInvalidOpusSampleRate);
91
92 auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream(
93 params.total_stream_count, params.stereo_stream_count)};
94 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
95 work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64);
96 work_buffer_size +=
97 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
98 out_size = work_buffer_size;
99 R_SUCCEED();
100}
101
102} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h
new file mode 100644
index 000000000..466e1967b
--- /dev/null
+++ b/src/audio_core/opus/decoder_manager.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/opus/hardware_opus.h"
7#include "audio_core/opus/parameters.h"
8#include "common/common_types.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace Core {
12class System;
13}
14
15namespace AudioCore::OpusDecoder {
16
17class OpusDecoderManager {
18public:
19 OpusDecoderManager(Core::System& system);
20
21 HardwareOpus& GetHardwareOpus() {
22 return hardware_opus;
23 }
24
25 Result GetWorkBufferSize(OpusParameters& params, u64& out_size);
26 Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size);
27 Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size);
28 Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size);
29 Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size);
30 Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size);
31
32private:
33 Core::System& system;
34 HardwareOpus hardware_opus;
35 std::array<u64, MaxChannels> required_workbuffer_sizes{};
36};
37
38} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp
new file mode 100644
index 000000000..d6544dcb0
--- /dev/null
+++ b/src/audio_core/opus/hardware_opus.cpp
@@ -0,0 +1,241 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5
6#include "audio_core/audio_core.h"
7#include "audio_core/opus/hardware_opus.h"
8#include "core/core.h"
9
10namespace AudioCore::OpusDecoder {
11namespace {
12using namespace Service::Audio;
13
14static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
15 s32 error{static_cast<s32>(error_code)};
16 ASSERT(error <= OPUS_OK);
17 switch (error) {
18 case OPUS_ALLOC_FAIL:
19 R_THROW(ResultLibOpusAllocFail);
20 case OPUS_INVALID_STATE:
21 R_THROW(ResultLibOpusInvalidState);
22 case OPUS_UNIMPLEMENTED:
23 R_THROW(ResultLibOpusUnimplemented);
24 case OPUS_INVALID_PACKET:
25 R_THROW(ResultLibOpusInvalidPacket);
26 case OPUS_INTERNAL_ERROR:
27 R_THROW(ResultLibOpusInternalError);
28 case OPUS_BUFFER_TOO_SMALL:
29 R_THROW(ResultBufferTooSmall);
30 case OPUS_BAD_ARG:
31 R_THROW(ResultLibOpusBadArg);
32 case OPUS_OK:
33 R_RETURN(ResultSuccess);
34 }
35 UNREACHABLE();
36}
37
38} // namespace
39
40HardwareOpus::HardwareOpus(Core::System& system_)
41 : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
42 opus_decoder.SetSharedMemory(shared_memory);
43}
44
45u64 HardwareOpus::GetWorkBufferSize(u32 channel) {
46 if (!opus_decoder.IsRunning()) {
47 return 0;
48 }
49 std::scoped_lock l{mutex};
50 shared_memory.host_send_data[0] = channel;
51 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize);
52 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
53 if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) {
54 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
55 ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg);
56 return 0;
57 }
58 return shared_memory.dsp_return_data[0];
59}
60
61u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) {
62 std::scoped_lock l{mutex};
63 shared_memory.host_send_data[0] = total_stream_count;
64 shared_memory.host_send_data[1] = stereo_stream_count;
65 opus_decoder.Send(ADSP::Direction::DSP,
66 ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream);
67 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
68 if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) {
69 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
70 ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg);
71 return 0;
72 }
73 return shared_memory.dsp_return_data[0];
74}
75
76Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
77 u64 buffer_size) {
78 std::scoped_lock l{mutex};
79 shared_memory.host_send_data[0] = (u64)buffer;
80 shared_memory.host_send_data[1] = buffer_size;
81 shared_memory.host_send_data[2] = sample_rate;
82 shared_memory.host_send_data[3] = channel_count;
83
84 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject);
85 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
86 if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) {
87 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
88 ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg);
89 R_THROW(ResultInvalidOpusDSPReturnCode);
90 }
91
92 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
93}
94
95Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
96 u32 total_stream_count,
97 u32 stereo_stream_count, void* mappings,
98 void* buffer, u64 buffer_size) {
99 std::scoped_lock l{mutex};
100 shared_memory.host_send_data[0] = (u64)buffer;
101 shared_memory.host_send_data[1] = buffer_size;
102 shared_memory.host_send_data[2] = sample_rate;
103 shared_memory.host_send_data[3] = channel_count;
104 shared_memory.host_send_data[4] = total_stream_count;
105 shared_memory.host_send_data[5] = stereo_stream_count;
106
107 ASSERT(channel_count <= MaxChannels);
108 std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
109
110 opus_decoder.Send(ADSP::Direction::DSP,
111 ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject);
112 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
113 if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) {
114 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
115 ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg);
116 R_THROW(ResultInvalidOpusDSPReturnCode);
117 }
118
119 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
120}
121
122Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
123 std::scoped_lock l{mutex};
124 shared_memory.host_send_data[0] = (u64)buffer;
125 shared_memory.host_send_data[1] = buffer_size;
126
127 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject);
128 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
129 ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK,
130 "Expected Opus shutdown code {}, got {}",
131 ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
132
133 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
134}
135
136Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
137 std::scoped_lock l{mutex};
138 shared_memory.host_send_data[0] = (u64)buffer;
139 shared_memory.host_send_data[1] = buffer_size;
140
141 opus_decoder.Send(ADSP::Direction::DSP,
142 ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject);
143 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
144 ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK,
145 "Expected Opus shutdown code {}, got {}",
146 ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
147
148 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
149}
150
151Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
152 u64 output_data_size, u32 channel_count, void* input_data,
153 u64 input_data_size, void* buffer, u64& out_time_taken,
154 bool reset) {
155 std::scoped_lock l{mutex};
156 shared_memory.host_send_data[0] = (u64)buffer;
157 shared_memory.host_send_data[1] = (u64)input_data;
158 shared_memory.host_send_data[2] = input_data_size;
159 shared_memory.host_send_data[3] = (u64)output_data;
160 shared_memory.host_send_data[4] = output_data_size;
161 shared_memory.host_send_data[5] = 0;
162 shared_memory.host_send_data[6] = reset;
163
164 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved);
165 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
166 if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) {
167 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
168 ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg);
169 R_THROW(ResultInvalidOpusDSPReturnCode);
170 }
171
172 auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
173 if (error_code == OPUS_OK) {
174 out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
175 out_time_taken = 1000 * shared_memory.dsp_return_data[2];
176 }
177 R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
178}
179
180Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
181 u64 output_data_size, u32 channel_count,
182 void* input_data, u64 input_data_size,
183 void* buffer, u64& out_time_taken,
184 bool reset) {
185 std::scoped_lock l{mutex};
186 shared_memory.host_send_data[0] = (u64)buffer;
187 shared_memory.host_send_data[1] = (u64)input_data;
188 shared_memory.host_send_data[2] = input_data_size;
189 shared_memory.host_send_data[3] = (u64)output_data;
190 shared_memory.host_send_data[4] = output_data_size;
191 shared_memory.host_send_data[5] = 0;
192 shared_memory.host_send_data[6] = reset;
193
194 opus_decoder.Send(ADSP::Direction::DSP,
195 ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
196 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
197 if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
198 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
199 ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
200 R_THROW(ResultInvalidOpusDSPReturnCode);
201 }
202
203 auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
204 if (error_code == OPUS_OK) {
205 out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
206 out_time_taken = 1000 * shared_memory.dsp_return_data[2];
207 }
208 R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
209}
210
211Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
212 std::scoped_lock l{mutex};
213 shared_memory.host_send_data[0] = (u64)buffer;
214 shared_memory.host_send_data[1] = buffer_size;
215
216 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
217 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
218 if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
219 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
220 ADSP::OpusDecoder::Message::MapMemoryOK, msg);
221 R_THROW(ResultInvalidOpusDSPReturnCode);
222 }
223 R_SUCCEED();
224}
225
226Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
227 std::scoped_lock l{mutex};
228 shared_memory.host_send_data[0] = (u64)buffer;
229 shared_memory.host_send_data[1] = buffer_size;
230
231 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
232 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
233 if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
234 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
235 ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
236 R_THROW(ResultInvalidOpusDSPReturnCode);
237 }
238 R_SUCCEED();
239}
240
241} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h
new file mode 100644
index 000000000..7013a6b40
--- /dev/null
+++ b/src/audio_core/opus/hardware_opus.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include <opus.h>
8
9#include "audio_core/adsp/apps/opus/opus_decoder.h"
10#include "audio_core/adsp/apps/opus/shared_memory.h"
11#include "audio_core/adsp/mailbox.h"
12#include "core/hle/service/audio/errors.h"
13
14namespace AudioCore::OpusDecoder {
15class HardwareOpus {
16public:
17 HardwareOpus(Core::System& system);
18
19 u64 GetWorkBufferSize(u32 channel);
20 u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
21
22 Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
23 u64 buffer_size);
24 Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
25 u32 totaL_stream_count, u32 stereo_stream_count,
26 void* mappings, void* buffer, u64 buffer_size);
27 Result ShutdownDecodeObject(void* buffer, u64 buffer_size);
28 Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size);
29 Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size,
30 u32 channel_count, void* input_data, u64 input_data_size, void* buffer,
31 u64& out_time_taken, bool reset);
32 Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
33 u64 output_data_size, u32 channel_count,
34 void* input_data, u64 input_data_size, void* buffer,
35 u64& out_time_taken, bool reset);
36 Result MapMemory(void* buffer, u64 buffer_size);
37 Result UnmapMemory(void* buffer, u64 buffer_size);
38
39private:
40 Core::System& system;
41 std::mutex mutex;
42 ADSP::OpusDecoder::OpusDecoder& opus_decoder;
43 ADSP::OpusDecoder::SharedMemory shared_memory;
44};
45} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h
new file mode 100644
index 000000000..4c54b2825
--- /dev/null
+++ b/src/audio_core/opus/parameters.h
@@ -0,0 +1,54 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace AudioCore::OpusDecoder {
10constexpr size_t OpusStreamCountMax = 255;
11constexpr size_t MaxChannels = 2;
12
13struct OpusParameters {
14 /* 0x00 */ u32 sample_rate;
15 /* 0x04 */ u32 channel_count;
16}; // size = 0x8
17static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!");
18
19struct OpusParametersEx {
20 /* 0x00 */ u32 sample_rate;
21 /* 0x04 */ u32 channel_count;
22 /* 0x08 */ bool use_large_frame_size;
23 /* 0x09 */ INSERT_PADDING_BYTES(7);
24}; // size = 0x10
25static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!");
26
27struct OpusMultiStreamParameters {
28 /* 0x00 */ u32 sample_rate;
29 /* 0x04 */ u32 channel_count;
30 /* 0x08 */ u32 total_stream_count;
31 /* 0x0C */ u32 stereo_stream_count;
32 /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings;
33}; // size = 0x110
34static_assert(sizeof(OpusMultiStreamParameters) == 0x110,
35 "OpusMultiStreamParameters has the wrong size!");
36
37struct OpusMultiStreamParametersEx {
38 /* 0x00 */ u32 sample_rate;
39 /* 0x04 */ u32 channel_count;
40 /* 0x08 */ u32 total_stream_count;
41 /* 0x0C */ u32 stereo_stream_count;
42 /* 0x10 */ bool use_large_frame_size;
43 /* 0x11 */ INSERT_PADDING_BYTES(7);
44 /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings;
45}; // size = 0x118
46static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
47 "OpusMultiStreamParametersEx has the wrong size!");
48
49struct OpusPacketHeader {
50 /* 0x00 */ u32 size;
51 /* 0x04 */ u32 final_range;
52}; // size = 0x8
53static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!");
54} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
index a48a016b1..0f7aff1b4 100644
--- a/src/audio_core/renderer/command/command_processing_time_estimator.cpp
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
@@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate(
27 27
28u32 CommandProcessingTimeEstimatorVersion1::Estimate( 28u32 CommandProcessingTimeEstimatorVersion1::Estimate(
29 const AdpcmDataSourceVersion1Command& command) const { 29 const AdpcmDataSourceVersion1Command& command) const {
30 return static_cast<u32>(command.pitch * 0.25f * 1.2f); 30 return static_cast<u32>(command.pitch * 0.46f * 1.2f);
31} 31}
32 32
33u32 CommandProcessingTimeEstimatorVersion1::Estimate( 33u32 CommandProcessingTimeEstimatorVersion1::Estimate(
34 const AdpcmDataSourceVersion2Command& command) const { 34 const AdpcmDataSourceVersion2Command& command) const {
35 return static_cast<u32>(command.pitch * 0.25f * 1.2f); 35 return static_cast<u32>(command.pitch * 0.46f * 1.2f);
36} 36}
37 37
38u32 CommandProcessingTimeEstimatorVersion1::Estimate( 38u32 CommandProcessingTimeEstimatorVersion1::Estimate(
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
index d29754634..31f92087c 100644
--- a/src/audio_core/renderer/system.cpp
+++ b/src/audio_core/renderer/system.cpp
@@ -684,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
684 sink_context, splitter_context, perf_manager}; 684 sink_context, splitter_context, perf_manager};
685 685
686 voice_context.SortInfo(); 686 voice_context.SortInfo();
687 command_generator.GenerateVoiceCommands();
687 688
688 const auto start_estimated_time{drop_voice_param * 689 const auto start_estimated_time{drop_voice_param *
689 static_cast<f32>(command_buffer.estimated_process_time)}; 690 static_cast<f32>(command_buffer.estimated_process_time)};
690 691
691 command_generator.GenerateVoiceCommands();
692 command_generator.GenerateSubMixCommands(); 692 command_generator.GenerateSubMixCommands();
693 command_generator.GenerateFinalMixCommands(); 693 command_generator.GenerateFinalMixCommands();
694 command_generator.GenerateSinkCommands(); 694 command_generator.GenerateSinkCommands();
@@ -708,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
708 708
709 const auto end_estimated_time{drop_voice_param * 709 const auto end_estimated_time{drop_voice_param *
710 static_cast<f32>(command_buffer.estimated_process_time)}; 710 static_cast<f32>(command_buffer.estimated_process_time)};
711
712 const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) *
713 (static_cast<f32>(render_time_limit_percent) / 100.0f)};
714
711 const auto estimated_time{start_estimated_time - end_estimated_time}; 715 const auto estimated_time{start_estimated_time - end_estimated_time};
712 716
713 const auto time_limit{static_cast<u32>( 717 const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))};
714 estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) *
715 (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
716 num_voices_dropped = 718 num_voices_dropped =
717 DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); 719 DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit);
718 } 720 }
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 34877b461..416203c59 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -26,12 +26,11 @@ add_library(common STATIC
26 assert.h 26 assert.h
27 atomic_helpers.h 27 atomic_helpers.h
28 atomic_ops.h 28 atomic_ops.h
29 detached_tasks.cpp
30 detached_tasks.h
31 bit_cast.h 29 bit_cast.h
32 bit_field.h 30 bit_field.h
33 bit_set.h 31 bit_set.h
34 bit_util.h 32 bit_util.h
33 bounded_threadsafe_queue.h
35 cityhash.cpp 34 cityhash.cpp
36 cityhash.h 35 cityhash.h
37 common_funcs.h 36 common_funcs.h
@@ -41,6 +40,8 @@ add_library(common STATIC
41 container_hash.h 40 container_hash.h
42 demangle.cpp 41 demangle.cpp
43 demangle.h 42 demangle.h
43 detached_tasks.cpp
44 detached_tasks.h
44 div_ceil.h 45 div_ceil.h
45 dynamic_library.cpp 46 dynamic_library.cpp
46 dynamic_library.h 47 dynamic_library.h
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h
index bd87aa09b..b36fc1de9 100644
--- a/src/common/bounded_threadsafe_queue.h
+++ b/src/common/bounded_threadsafe_queue.h
@@ -45,13 +45,13 @@ public:
45 } 45 }
46 46
47 T PopWait() { 47 T PopWait() {
48 T t; 48 T t{};
49 Pop<PopMode::Wait>(t); 49 Pop<PopMode::Wait>(t);
50 return t; 50 return t;
51 } 51 }
52 52
53 T PopWait(std::stop_token stop_token) { 53 T PopWait(std::stop_token stop_token) {
54 T t; 54 T t{};
55 Pop<PopMode::WaitWithStopToken>(t, stop_token); 55 Pop<PopMode::WaitWithStopToken>(t, stop_token);
56 return t; 56 return t;
57 } 57 }
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 36e67c145..174aed49b 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
528// Generic Filesystem Operations 528// Generic Filesystem Operations
529 529
530bool Exists(const fs::path& path) { 530bool Exists(const fs::path& path) {
531 std::error_code ec;
531#ifdef ANDROID 532#ifdef ANDROID
532 if (Android::IsContentUri(path)) { 533 if (Android::IsContentUri(path)) {
533 return Android::Exists(path); 534 return Android::Exists(path);
534 } else { 535 } else {
535 return fs::exists(path); 536 return fs::exists(path, ec);
536 } 537 }
537#else 538#else
538 return fs::exists(path); 539 return fs::exists(path, ec);
539#endif 540#endif
540} 541}
541 542
542bool IsFile(const fs::path& path) { 543bool IsFile(const fs::path& path) {
544 std::error_code ec;
543#ifdef ANDROID 545#ifdef ANDROID
544 if (Android::IsContentUri(path)) { 546 if (Android::IsContentUri(path)) {
545 return !Android::IsDirectory(path); 547 return !Android::IsDirectory(path);
546 } else { 548 } else {
547 return fs::is_regular_file(path); 549 return fs::is_regular_file(path, ec);
548 } 550 }
549#else 551#else
550 return fs::is_regular_file(path); 552 return fs::is_regular_file(path, ec);
551#endif 553#endif
552} 554}
553 555
554bool IsDir(const fs::path& path) { 556bool IsDir(const fs::path& path) {
557 std::error_code ec;
555#ifdef ANDROID 558#ifdef ANDROID
556 if (Android::IsContentUri(path)) { 559 if (Android::IsContentUri(path)) {
557 return Android::IsDirectory(path); 560 return Android::IsDirectory(path);
558 } else { 561 } else {
559 return fs::is_directory(path); 562 return fs::is_directory(path, ec);
560 } 563 }
561#else 564#else
562 return fs::is_directory(path); 565 return fs::is_directory(path, ec);
563#endif 566#endif
564} 567}
565 568
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 4ecaf550b..3fde3cae6 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -130,13 +130,17 @@ void LogSettings() {
130 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); 130 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
131} 131}
132 132
133void UpdateGPUAccuracy() {
134 values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
135}
136
133bool IsGPULevelExtreme() { 137bool IsGPULevelExtreme() {
134 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme; 138 return values.current_gpu_accuracy == GpuAccuracy::Extreme;
135} 139}
136 140
137bool IsGPULevelHigh() { 141bool IsGPULevelHigh() {
138 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme || 142 return values.current_gpu_accuracy == GpuAccuracy::Extreme ||
139 values.gpu_accuracy.GetValue() == GpuAccuracy::High; 143 values.current_gpu_accuracy == GpuAccuracy::High;
140} 144}
141 145
142bool IsFastmemEnabled() { 146bool IsFastmemEnabled() {
diff --git a/src/common/settings.h b/src/common/settings.h
index b15213bd7..98ab0ec2e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -307,6 +307,7 @@ struct Values {
307 Specialization::Default, 307 Specialization::Default,
308 true, 308 true,
309 true}; 309 true};
310 GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
310 SwitchableSetting<AnisotropyMode, true> max_anisotropy{ 311 SwitchableSetting<AnisotropyMode, true> max_anisotropy{
311 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, 312 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
312 "max_anisotropy", Category::RendererAdvanced}; 313 "max_anisotropy", Category::RendererAdvanced};
@@ -348,6 +349,10 @@ struct Values {
348 Category::RendererDebug}; 349 Category::RendererDebug};
349 Setting<bool> disable_shader_loop_safety_checks{ 350 Setting<bool> disable_shader_loop_safety_checks{
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 351 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
352 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
353 Category::RendererDebug};
354 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
355 bool renderer_amdvlk_depth_bias_workaround{};
351 356
352 // System 357 // System
353 SwitchableSetting<Language, true> language_index{linkage, 358 SwitchableSetting<Language, true> language_index{linkage,
@@ -520,6 +525,7 @@ struct Values {
520 525
521extern Values values; 526extern Values values;
522 527
528void UpdateGPUAccuracy();
523bool IsGPULevelExtreme(); 529bool IsGPULevelExtreme();
524bool IsGPULevelHigh(); 530bool IsGPULevelHigh();
525 531
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 5b170dfd5..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -225,6 +225,16 @@ public:
225 */ 225 */
226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; 226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
227 227
228 /**
229 * @returns True if the underlying type is a floating point storage
230 */
231 [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
232
233 /**
234 * @returns True if the underlying type is an integer storage
235 */
236 [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
237
228 /* 238 /*
229 * Switchable settings 239 * Switchable settings
230 */ 240 */
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index e10843c73..3175ab07d 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
10#include <string> 10#include <string>
11#include <typeindex> 11#include <typeindex>
12#include <typeinfo> 12#include <typeinfo>
13#include <fmt/core.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
14#include "common/settings_common.h" 15#include "common/settings_common.h"
15#include "common/settings_enums.h" 16#include "common/settings_enums.h"
@@ -115,8 +116,12 @@ protected:
115 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 116 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
116 // Compatibility with old AudioEngine setting being a string 117 // Compatibility with old AudioEngine setting being a string
117 return CanonicalizeEnum(value_); 118 return CanonicalizeEnum(value_);
119 } else if constexpr (std::is_floating_point_v<Type>) {
120 return fmt::format("{:f}", value_);
121 } else if constexpr (std::is_enum_v<Type>) {
122 return std::to_string(static_cast<u32>(value_));
118 } else { 123 } else {
119 return std::to_string(static_cast<u64>(value_)); 124 return std::to_string(value_);
120 } 125 }
121 } 126 }
122 127
@@ -180,13 +185,17 @@ public:
180 this->SetValue(static_cast<u32>(std::stoul(input))); 185 this->SetValue(static_cast<u32>(std::stoul(input)));
181 } else if constexpr (std::is_same_v<Type, bool>) { 186 } else if constexpr (std::is_same_v<Type, bool>) {
182 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
188 } else if constexpr (std::is_same_v<Type, float>) {
189 this->SetValue(std::stof(input));
183 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 190 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
184 this->SetValue(ToEnum<Type>(input)); 191 this->SetValue(ToEnum<AudioEngine>(input));
185 } else { 192 } else {
186 this->SetValue(static_cast<Type>(std::stoll(input))); 193 this->SetValue(static_cast<Type>(std::stoll(input)));
187 } 194 }
188 } catch (std::invalid_argument&) { 195 } catch (std::invalid_argument&) {
189 this->SetValue(this->GetDefault()); 196 this->SetValue(this->GetDefault());
197 } catch (std::out_of_range&) {
198 this->SetValue(this->GetDefault());
190 } 199 }
191 } 200 }
192 201
@@ -215,11 +224,27 @@ public:
215 } 224 }
216 } 225 }
217 226
227 [[nodiscard]] constexpr bool IsFloatingPoint() const final {
228 return std::is_floating_point_v<Type>;
229 }
230
231 [[nodiscard]] constexpr bool IsIntegral() const final {
232 return std::is_integral_v<Type>;
233 }
234
218 [[nodiscard]] std::string MinVal() const override final { 235 [[nodiscard]] std::string MinVal() const override final {
219 return this->ToString(minimum); 236 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
237 return this->ToString(std::numeric_limits<Type>::min());
238 } else {
239 return this->ToString(minimum);
240 }
220 } 241 }
221 [[nodiscard]] std::string MaxVal() const override final { 242 [[nodiscard]] std::string MaxVal() const override final {
222 return this->ToString(maximum); 243 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
244 return this->ToString(std::numeric_limits<Type>::max());
245 } else {
246 return this->ToString(maximum);
247 }
223 } 248 }
224 249
225 [[nodiscard]] constexpr bool Ranged() const override { 250 [[nodiscard]] constexpr bool Ranged() const override {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6cd1a28f2..d0f76e57e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -596,6 +596,10 @@ add_library(core STATIC
596 hle/service/mii/types/ver3_store_data.h 596 hle/service/mii/types/ver3_store_data.h
597 hle/service/mii/mii.cpp 597 hle/service/mii/mii.cpp
598 hle/service/mii/mii.h 598 hle/service/mii/mii.h
599 hle/service/mii/mii_database.cpp
600 hle/service/mii/mii_database.h
601 hle/service/mii/mii_database_manager.cpp
602 hle/service/mii/mii_database_manager.h
599 hle/service/mii/mii_manager.cpp 603 hle/service/mii/mii_manager.cpp
600 hle/service/mii/mii_manager.h 604 hle/service/mii/mii_manager.h
601 hle/service/mii/mii_result.h 605 hle/service/mii/mii_result.h
@@ -864,6 +868,8 @@ add_library(core STATIC
864 telemetry_session.h 868 telemetry_session.h
865 tools/freezer.cpp 869 tools/freezer.cpp
866 tools/freezer.h 870 tools/freezer.h
871 tools/renderdoc.cpp
872 tools/renderdoc.h
867) 873)
868 874
869if (MSVC) 875if (MSVC)
@@ -879,6 +885,7 @@ else()
879 -Werror=conversion 885 -Werror=conversion
880 886
881 -Wno-sign-conversion 887 -Wno-sign-conversion
888 -Wno-cast-function-type
882 889
883 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> 890 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
884 ) 891 )
@@ -887,7 +894,7 @@ endif()
887create_target_directory_groups(core) 894create_target_directory_groups(core)
888 895
889target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 896target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
890target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 897target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
891if (MINGW) 898if (MINGW)
892 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 899 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
893endif() 900endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2d6e61398..08cbb8978 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,6 +51,7 @@
51#include "core/reporter.h" 51#include "core/reporter.h"
52#include "core/telemetry_session.h" 52#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 53#include "core/tools/freezer.h"
54#include "core/tools/renderdoc.h"
54#include "network/network.h" 55#include "network/network.h"
55#include "video_core/host1x/host1x.h" 56#include "video_core/host1x/host1x.h"
56#include "video_core/renderer_base.h" 57#include "video_core/renderer_base.h"
@@ -281,6 +282,10 @@ struct System::Impl {
281 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); 282 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
282 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); 283 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
283 284
285 if (Settings::values.enable_renderdoc_hotkey) {
286 renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
287 }
288
284 LOG_DEBUG(Core, "Initialized OK"); 289 LOG_DEBUG(Core, "Initialized OK");
285 290
286 return SystemResultStatus::Success; 291 return SystemResultStatus::Success;
@@ -376,6 +381,10 @@ struct System::Impl {
376 room_member->SendGameInfo(game_info); 381 room_member->SendGameInfo(game_info);
377 } 382 }
378 383
384 // Workarounds:
385 // Activate this in Super Smash Brothers Ultimate, it only affects AMD cards using AMDVLK
386 Settings::values.renderer_amdvlk_depth_bias_workaround = program_id == 0x1006A800016E000ULL;
387
379 status = SystemResultStatus::Success; 388 status = SystemResultStatus::Success;
380 return status; 389 return status;
381 } 390 }
@@ -435,6 +444,9 @@ struct System::Impl {
435 room_member->SendGameInfo(game_info); 444 room_member->SendGameInfo(game_info);
436 } 445 }
437 446
447 // Workarounds
448 Settings::values.renderer_amdvlk_depth_bias_workaround = false;
449
438 LOG_DEBUG(Core, "Shutdown OK"); 450 LOG_DEBUG(Core, "Shutdown OK");
439 } 451 }
440 452
@@ -521,6 +533,8 @@ struct System::Impl {
521 std::unique_ptr<Tools::Freezer> memory_freezer; 533 std::unique_ptr<Tools::Freezer> memory_freezer;
522 std::array<u8, 0x20> build_id{}; 534 std::array<u8, 0x20> build_id{};
523 535
536 std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
537
524 /// Frontend applets 538 /// Frontend applets
525 Service::AM::Applets::AppletManager applet_manager; 539 Service::AM::Applets::AppletManager applet_manager;
526 540
@@ -1024,6 +1038,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
1024 return impl->room_network; 1038 return impl->room_network;
1025} 1039}
1026 1040
1041Tools::RenderdocAPI& System::GetRenderdocAPI() {
1042 return *impl->renderdoc_api;
1043}
1044
1027void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1045void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
1028 return impl->kernel.RunServer(std::move(server_manager)); 1046 return impl->kernel.RunServer(std::move(server_manager));
1029} 1047}
diff --git a/src/core/core.h b/src/core/core.h
index fba312125..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -102,6 +102,10 @@ namespace Network {
102class RoomNetwork; 102class RoomNetwork;
103} 103}
104 104
105namespace Tools {
106class RenderdocAPI;
107}
108
105namespace Core { 109namespace Core {
106 110
107class ARM_Interface; 111class ARM_Interface;
@@ -413,6 +417,8 @@ public:
413 /// Gets an immutable reference to the Room Network. 417 /// Gets an immutable reference to the Room Network.
414 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 418 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
415 419
420 [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
421
416 void SetExitLocked(bool locked); 422 void SetExitLocked(bool locked);
417 bool GetExitLocked() const; 423 bool GetExitLocked() const;
418 424
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index e13c5cdc7..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
724 continue; 724 continue;
725 } 725 }
726 726
727 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 727 const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
730 if (!ValidCryptoRevisionString(out[0], 18, 2)) { 730 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
731 continue; 731 continue;
732 } 732 }
733 733
734 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 734 const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { 736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
@@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
750 } 750 }
751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
752 const auto index = 752 const auto index =
753 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 753 std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
754 const auto sub = kv.first.second; 754 const auto sub = kv.first.second;
755 if (sub == 0) { 755 if (sub == 0) {
756 s128_keys[{kv.first.first, index, 0}] = 756 s128_keys[{kv.first.first, index, 0}] =
@@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
770 const auto& match = kak_names[j]; 770 const auto& match = kak_names[j];
771 if (out[0].compare(0, std::strlen(match), match) == 0) { 771 if (out[0].compare(0, std::strlen(match), match) == 0) {
772 const auto index = 772 const auto index =
773 std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); 773 std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
774 s128_keys[{S128KeyType::KeyArea, index, j}] = 774 s128_keys[{S128KeyType::KeyArea, index, j}] =
775 Common::HexStringToArray<16>(out[1]); 775 Common::HexStringToArray<16>(out[1]);
776 } 776 }
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void IPSwitchCompiler::ParseFlag(const std::string& line) {
166 if (StartsWith(line, "@flag offset_shift ")) { 166 if (StartsWith(line, "@flag offset_shift ")) {
167 // Offset Shift Flag 167 // Offset Shift Flag
168 offset_shift = std::stoll(line.substr(19), nullptr, 0); 168 offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
169 } else if (StartsWith(line, "@little-endian")) { 169 } else if (StartsWith(line, "@little-endian")) {
170 // Set values to read as little endian 170 // Set values to read as little endian
171 is_little_endian = true; 171 is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val 263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
264 if (patch_line.length() < 11) 264 if (patch_line.length() < 11)
265 break; 265 break;
266 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); 266 auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
267 offset += static_cast<unsigned long>(offset_shift); 267 offset += static_cast<unsigned long>(offset_shift);
268 268
269 std::vector<u8> replace; 269 std::vector<u8> replace;
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 2527ae606..2422cb51b 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
47 // Actually read in now... 47 // Actually read in now...
48 std::vector<u8> file_data = file->ReadBytes(metadata_size); 48 std::vector<u8> file_data = file->ReadBytes(metadata_size);
49 const std::size_t total_size = file_data.size(); 49 const std::size_t total_size = file_data.size();
50 file_data.push_back(0);
50 51
51 if (total_size != metadata_size) { 52 if (total_size != metadata_size) {
52 status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; 53 status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index a4baddb15..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
294 return out; 294 return out;
295} 295}
296 296
297bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { 297bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
298 const auto build_id_raw = Common::HexToString(build_id_); 298 const auto build_id_raw = Common::HexToString(build_id_);
299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
300 300
301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); 301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
302 302
303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
304 if (load_dir == nullptr) { 304 if (load_dir == nullptr) {
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index adcde7b7d..03e9c7301 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -52,7 +52,7 @@ public:
52 52
53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
54 // Used to prevent expensive copies in NSO loader. 54 // Used to prevent expensive copies in NSO loader.
55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; 55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
56 56
57 // Creates a CheatList object with all 57 // Creates a CheatList object with all
58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( 58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index e33b00d89..04da93d5c 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -752,7 +752,9 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
752 for (u8 i = 0; i < 0x10; i++) { 752 for (u8 i = 0; i < 0x10; i++) {
753 const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); 753 const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
754 const auto filename = GetCNMTName(TitleType::Update, title_id + i); 754 const auto filename = GetCNMTName(TitleType::Update, title_id + i);
755 removed_data |= meta_dir->DeleteFile(filename); 755 if (meta_dir->GetFile(filename)) {
756 removed_data |= meta_dir->DeleteFile(filename);
757 }
756 } 758 }
757 759
758 return removed_data; 760 return removed_data;
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 94bd656fe..2af3f06fc 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -542,6 +542,7 @@ void EmulatedController::UnloadInput() {
542} 542}
543 543
544void EmulatedController::EnableConfiguration() { 544void EmulatedController::EnableConfiguration() {
545 std::scoped_lock lock{connect_mutex, npad_mutex};
545 is_configuring = true; 546 is_configuring = true;
546 tmp_is_connected = is_connected; 547 tmp_is_connected = is_connected;
547 tmp_npad_type = npad_type; 548 tmp_npad_type = npad_type;
@@ -1556,7 +1557,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
1556 1557
1557 auto trigger_guard = 1558 auto trigger_guard =
1558 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }); 1559 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
1559 std::scoped_lock lock{mutex}; 1560 std::scoped_lock lock{connect_mutex, mutex};
1560 if (is_configuring) { 1561 if (is_configuring) {
1561 tmp_is_connected = true; 1562 tmp_is_connected = true;
1562 return; 1563 return;
@@ -1572,7 +1573,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
1572void EmulatedController::Disconnect() { 1573void EmulatedController::Disconnect() {
1573 auto trigger_guard = 1574 auto trigger_guard =
1574 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }); 1575 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
1575 std::scoped_lock lock{mutex}; 1576 std::scoped_lock lock{connect_mutex, mutex};
1576 if (is_configuring) { 1577 if (is_configuring) {
1577 tmp_is_connected = false; 1578 tmp_is_connected = false;
1578 return; 1579 return;
@@ -1586,7 +1587,7 @@ void EmulatedController::Disconnect() {
1586} 1587}
1587 1588
1588bool EmulatedController::IsConnected(bool get_temporary_value) const { 1589bool EmulatedController::IsConnected(bool get_temporary_value) const {
1589 std::scoped_lock lock{mutex}; 1590 std::scoped_lock lock{connect_mutex};
1590 if (get_temporary_value && is_configuring) { 1591 if (get_temporary_value && is_configuring) {
1591 return tmp_is_connected; 1592 return tmp_is_connected;
1592 } 1593 }
@@ -1599,7 +1600,7 @@ NpadIdType EmulatedController::GetNpadIdType() const {
1599} 1600}
1600 1601
1601NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { 1602NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
1602 std::scoped_lock lock{mutex}; 1603 std::scoped_lock lock{npad_mutex};
1603 if (get_temporary_value && is_configuring) { 1604 if (get_temporary_value && is_configuring) {
1604 return tmp_npad_type; 1605 return tmp_npad_type;
1605 } 1606 }
@@ -1609,7 +1610,7 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
1609void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { 1610void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
1610 auto trigger_guard = 1611 auto trigger_guard =
1611 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }); 1612 SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
1612 std::scoped_lock lock{mutex}; 1613 std::scoped_lock lock{mutex, npad_mutex};
1613 1614
1614 if (is_configuring) { 1615 if (is_configuring) {
1615 if (tmp_npad_type == npad_type_) { 1616 if (tmp_npad_type == npad_type_) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 88d77db8d..d4500583e 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -603,6 +603,8 @@ private:
603 603
604 mutable std::mutex mutex; 604 mutable std::mutex mutex;
605 mutable std::mutex callback_mutex; 605 mutable std::mutex callback_mutex;
606 mutable std::mutex npad_mutex;
607 mutable std::mutex connect_mutex;
606 std::unordered_map<int, ControllerUpdateCallback> callback_list; 608 std::unordered_map<int, ControllerUpdateCallback> callback_list;
607 int last_callback_key = 0; 609 int last_callback_key = 0;
608 610
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
index 7d6373414..cf53c04d9 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
154 return NpadIdType::Player1; 154 return NpadIdType::Player1;
155} 155}
156 156
157void HIDCore::SetLastActiveController(NpadIdType npad_id) {
158 last_active_controller = npad_id;
159}
160
161NpadIdType HIDCore::GetLastActiveController() const {
162 return last_active_controller;
163}
164
157void HIDCore::EnableAllControllerConfiguration() { 165void HIDCore::EnableAllControllerConfiguration() {
158 player_1->EnableConfiguration(); 166 player_1->EnableConfiguration();
159 player_2->EnableConfiguration(); 167 player_2->EnableConfiguration();
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
index 5fe36551e..80abab18b 100644
--- a/src/core/hid/hid_core.h
+++ b/src/core/hid/hid_core.h
@@ -48,6 +48,12 @@ public:
48 /// Returns the first disconnected npad id 48 /// Returns the first disconnected npad id
49 NpadIdType GetFirstDisconnectedNpadId() const; 49 NpadIdType GetFirstDisconnectedNpadId() const;
50 50
51 /// Sets the npad id of the last active controller
52 void SetLastActiveController(NpadIdType npad_id);
53
54 /// Returns the npad id of the last controller that pushed a button
55 NpadIdType GetLastActiveController() const;
56
51 /// Sets all emulated controllers into configuring mode. 57 /// Sets all emulated controllers into configuring mode.
52 void EnableAllControllerConfiguration(); 58 void EnableAllControllerConfiguration();
53 59
@@ -77,6 +83,7 @@ private:
77 std::unique_ptr<EmulatedConsole> console; 83 std::unique_ptr<EmulatedConsole> console;
78 std::unique_ptr<EmulatedDevices> devices; 84 std::unique_ptr<EmulatedDevices> devices;
79 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 85 NpadStyleTag supported_style_tag{NpadStyleSet::All};
86 NpadIdType last_active_controller{NpadIdType::Handheld};
80}; 87};
81 88
82} // namespace Core::HID 89} // namespace Core::HID
diff --git a/src/core/hle/kernel/k_hardware_timer.cpp b/src/core/hle/kernel/k_hardware_timer.cpp
index 4dcd53821..8e2e40307 100644
--- a/src/core/hle/kernel/k_hardware_timer.cpp
+++ b/src/core/hle/kernel/k_hardware_timer.cpp
@@ -35,7 +35,9 @@ void KHardwareTimer::DoTask() {
35 } 35 }
36 36
37 // Disable the timer interrupt while we handle this. 37 // Disable the timer interrupt while we handle this.
38 this->DisableInterrupt(); 38 // Not necessary due to core timing already having popped this event to call it.
39 // this->DisableInterrupt();
40 m_wakeup_time = std::numeric_limits<s64>::max();
39 41
40 if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); 42 if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
41 0 < next_time && next_time <= m_wakeup_time) { 43 0 < next_time && next_time <= m_wakeup_time) {
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 703049ede..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
96 process->m_is_suspended = false; 96 process->m_is_suspended = false;
97 process->m_schedule_count = 0; 97 process->m_schedule_count = 0;
98 process->m_is_handle_table_initialized = false; 98 process->m_is_handle_table_initialized = false;
99 process->m_is_hbl = false;
99 100
100 // Open a reference to the resource limit. 101 // Open a reference to the resource limit.
101 process->m_resource_limit->Open(); 102 process->m_resource_limit->Open();
@@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) {
351 R_SUCCEED(); 352 R_SUCCEED();
352} 353}
353 354
354Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { 355Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
356 bool is_hbl) {
355 m_program_id = metadata.GetTitleID(); 357 m_program_id = metadata.GetTitleID();
356 m_ideal_core = metadata.GetMainThreadCore(); 358 m_ideal_core = metadata.GetMainThreadCore();
357 m_is_64bit_process = metadata.Is64BitProgram(); 359 m_is_64bit_process = metadata.Is64BitProgram();
358 m_system_resource_size = metadata.GetSystemResourceSize(); 360 m_system_resource_size = metadata.GetSystemResourceSize();
359 m_image_size = code_size; 361 m_image_size = code_size;
362 m_is_hbl = is_hbl;
360 363
361 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { 364 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
362 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. 365 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 4fdeaf11a..146e07a57 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -338,7 +338,8 @@ public:
338 * @returns ResultSuccess if all relevant metadata was able to be 338 * @returns ResultSuccess if all relevant metadata was able to be
339 * loaded and parsed. Otherwise, an error code is returned. 339 * loaded and parsed. Otherwise, an error code is returned.
340 */ 340 */
341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); 341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
342 bool is_hbl);
342 343
343 /** 344 /**
344 * Starts the main application thread for this process. 345 * Starts the main application thread for this process.
@@ -368,6 +369,10 @@ public:
368 return GetProcessId(); 369 return GetProcessId();
369 } 370 }
370 371
372 bool IsHbl() const {
373 return m_is_hbl;
374 }
375
371 bool IsSignaled() const override; 376 bool IsSignaled() const override;
372 377
373 void DoWorkerTaskImpl(); 378 void DoWorkerTaskImpl();
@@ -525,6 +530,7 @@ private:
525 bool m_is_immortal{}; 530 bool m_is_immortal{};
526 bool m_is_handle_table_initialized{}; 531 bool m_is_handle_table_initialized{};
527 bool m_is_initialized{}; 532 bool m_is_initialized{};
533 bool m_is_hbl{};
528 534
529 std::atomic<u16> m_num_running_threads{}; 535 std::atomic<u16> m_num_running_threads{};
530 536
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp
index 4c14ce668..00b65429b 100644
--- a/src/core/hle/kernel/svc/svc_debug_string.cpp
+++ b/src/core/hle/kernel/svc/svc_debug_string.cpp
@@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
14 14
15 std::string str(len, '\0'); 15 std::string str(len, '\0');
16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); 16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
17 LOG_DEBUG(Debug_Emulated, "{}", str); 17 LOG_INFO(Debug_Emulated, "{}", str);
18 18
19 R_SUCCEED(); 19 R_SUCCEED();
20} 20}
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp
index 580cf2f75..c581c086b 100644
--- a/src/core/hle/kernel/svc/svc_exception.cpp
+++ b/src/core/hle/kernel/svc/svc_exception.cpp
@@ -3,6 +3,7 @@
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/debugger/debugger.h" 5#include "core/debugger/debugger.h"
6#include "core/hle/kernel/k_process.h"
6#include "core/hle/kernel/k_thread.h" 7#include "core/hle/kernel/k_thread.h"
7#include "core/hle/kernel/svc.h" 8#include "core/hle/kernel/svc.h"
8#include "core/hle/kernel/svc_types.h" 9#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
107 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); 108 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
108 } 109 }
109 110
110 if (system.DebuggerEnabled()) { 111 const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
112 const bool should_break = is_hbl || !notification_only;
113
114 if (system.DebuggerEnabled() && should_break) {
111 auto* thread = system.Kernel().GetCurrentEmuThread(); 115 auto* thread = system.Kernel().GetCurrentEmuThread();
112 system.GetDebugger().NotifyThreadStopped(thread); 116 system.GetDebugger().NotifyThreadStopped(thread);
113 thread->RequestSuspend(Kernel::SuspendType::Debug); 117 thread->RequestSuspend(Kernel::SuspendType::Debug);
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 92a1439eb..dd0b27f47 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
62 XCD = 108, 62 XCD = 108,
63 TMP451 = 109, 63 TMP451 = 109,
64 NIFM = 110, 64 NIFM = 110,
65 Hwopus = 111, 65 HwOpus = 111,
66 LSM6DS3 = 112, 66 LSM6DS3 = 112,
67 Bluetooth = 113, 67 Bluetooth = 113,
68 VI = 114, 68 VI = 114,
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index f9c4f9678..a83622f7c 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -19,6 +19,7 @@
19#include "core/hle/service/am/am.h" 19#include "core/hle/service/am/am.h"
20#include "core/hle/service/am/applet_ae.h" 20#include "core/hle/service/am/applet_ae.h"
21#include "core/hle/service/am/applet_oe.h" 21#include "core/hle/service/am/applet_oe.h"
22#include "core/hle/service/am/applets/applet_mii_edit_types.h"
22#include "core/hle/service/am/applets/applet_profile_select.h" 23#include "core/hle/service/am/applets/applet_profile_select.h"
23#include "core/hle/service/am/applets/applet_web_browser.h" 24#include "core/hle/service/am/applets/applet_web_browser.h"
24#include "core/hle/service/am/applets/applets.h" 25#include "core/hle/service/am/applets/applets.h"
@@ -190,7 +191,7 @@ IDisplayController::IDisplayController(Core::System& system_)
190 {5, nullptr, "GetLastForegroundCaptureImageEx"}, 191 {5, nullptr, "GetLastForegroundCaptureImageEx"},
191 {6, nullptr, "GetLastApplicationCaptureImageEx"}, 192 {6, nullptr, "GetLastApplicationCaptureImageEx"},
192 {7, nullptr, "GetCallerAppletCaptureImageEx"}, 193 {7, nullptr, "GetCallerAppletCaptureImageEx"},
193 {8, nullptr, "TakeScreenShotOfOwnLayer"}, 194 {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
194 {9, nullptr, "CopyBetweenCaptureBuffers"}, 195 {9, nullptr, "CopyBetweenCaptureBuffers"},
195 {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, 196 {10, nullptr, "AcquireLastApplicationCaptureBuffer"},
196 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, 197 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
@@ -218,6 +219,13 @@ IDisplayController::IDisplayController(Core::System& system_)
218 219
219IDisplayController::~IDisplayController() = default; 220IDisplayController::~IDisplayController() = default;
220 221
222void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
223 LOG_WARNING(Service_AM, "(STUBBED) called");
224
225 IPC::ResponseBuilder rb{ctx, 2};
226 rb.Push(ResultSuccess);
227}
228
221IDebugFunctions::IDebugFunctions(Core::System& system_) 229IDebugFunctions::IDebugFunctions(Core::System& system_)
222 : ServiceFramework{system_, "IDebugFunctions"} { 230 : ServiceFramework{system_, "IDebugFunctions"} {
223 // clang-format off 231 // clang-format off
@@ -724,7 +732,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
724 {110, nullptr, "OpenMyGpuErrorHandler"}, 732 {110, nullptr, "OpenMyGpuErrorHandler"},
725 {120, nullptr, "GetAppletLaunchedHistory"}, 733 {120, nullptr, "GetAppletLaunchedHistory"},
726 {200, nullptr, "GetOperationModeSystemInfo"}, 734 {200, nullptr, "GetOperationModeSystemInfo"},
727 {300, nullptr, "GetSettingsPlatformRegion"}, 735 {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
728 {400, nullptr, "ActivateMigrationService"}, 736 {400, nullptr, "ActivateMigrationService"},
729 {401, nullptr, "DeactivateMigrationService"}, 737 {401, nullptr, "DeactivateMigrationService"},
730 {500, nullptr, "DisableSleepTillShutdown"}, 738 {500, nullptr, "DisableSleepTillShutdown"},
@@ -736,6 +744,10 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
736 // clang-format on 744 // clang-format on
737 745
738 RegisterHandlers(functions); 746 RegisterHandlers(functions);
747
748 // Configure applets to be in foreground state
749 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
750 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
739} 751}
740 752
741ICommonStateGetter::~ICommonStateGetter() = default; 753ICommonStateGetter::~ICommonStateGetter() = default;
@@ -867,6 +879,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
867 rb.Push(ResultSuccess); 879 rb.Push(ResultSuccess);
868} 880}
869 881
882void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
883 LOG_WARNING(Service_AM, "(STUBBED) called");
884
885 IPC::ResponseBuilder rb{ctx, 3};
886 rb.Push(ResultSuccess);
887 rb.PushEnum(SysPlatformRegion::Global);
888}
889
870void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( 890void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
871 HLERequestContext& ctx) { 891 HLERequestContext& ctx) {
872 LOG_WARNING(Service_AM, "(STUBBED) called"); 892 LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1324,18 +1344,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
1324 1344
1325ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) 1345ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1326 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { 1346 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
1347 // clang-format off
1327 static const FunctionInfo functions[] = { 1348 static const FunctionInfo functions[] = {
1328 {0, nullptr, "PopInData"}, 1349 {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
1329 {1, nullptr, "PushOutData"}, 1350 {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
1330 {2, nullptr, "PopInteractiveInData"}, 1351 {2, nullptr, "PopInteractiveInData"},
1331 {3, nullptr, "PushInteractiveOutData"}, 1352 {3, nullptr, "PushInteractiveOutData"},
1332 {5, nullptr, "GetPopInDataEvent"}, 1353 {5, nullptr, "GetPopInDataEvent"},
1333 {6, nullptr, "GetPopInteractiveInDataEvent"}, 1354 {6, nullptr, "GetPopInteractiveInDataEvent"},
1334 {10, nullptr, "ExitProcessAndReturn"}, 1355 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
1335 {11, nullptr, "GetLibraryAppletInfo"}, 1356 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
1336 {12, nullptr, "GetMainAppletIdentityInfo"}, 1357 {12, nullptr, "GetMainAppletIdentityInfo"},
1337 {13, nullptr, "CanUseApplicationCore"}, 1358 {13, nullptr, "CanUseApplicationCore"},
1338 {14, nullptr, "GetCallerAppletIdentityInfo"}, 1359 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
1339 {15, nullptr, "GetMainAppletApplicationControlProperty"}, 1360 {15, nullptr, "GetMainAppletApplicationControlProperty"},
1340 {16, nullptr, "GetMainAppletStorageId"}, 1361 {16, nullptr, "GetMainAppletStorageId"},
1341 {17, nullptr, "GetCallerAppletIdentityInfoStack"}, 1362 {17, nullptr, "GetCallerAppletIdentityInfoStack"},
@@ -1361,10 +1382,142 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1361 {140, nullptr, "SetApplicationMemoryReservation"}, 1382 {140, nullptr, "SetApplicationMemoryReservation"},
1362 {150, nullptr, "ShouldSetGpuTimeSliceManually"}, 1383 {150, nullptr, "ShouldSetGpuTimeSliceManually"},
1363 }; 1384 };
1385 // clang-format on
1364 RegisterHandlers(functions); 1386 RegisterHandlers(functions);
1387
1388 PushInShowMiiEditData();
1365} 1389}
1366 1390
1367ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; 1391ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
1392void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
1393 LOG_INFO(Service_AM, "called");
1394
1395 if (queue_data.empty()) {
1396 IPC::ResponseBuilder rb{ctx, 2};
1397 rb.Push(ResultNoDataInChannel);
1398 return;
1399 }
1400
1401 auto data = queue_data.front();
1402 queue_data.pop_front();
1403
1404 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1405 rb.Push(ResultSuccess);
1406 rb.PushIpcInterface<IStorage>(system, std::move(data));
1407}
1408
1409void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
1410 LOG_WARNING(Service_AM, "(STUBBED) called");
1411
1412 IPC::ResponseBuilder rb{ctx, 2};
1413 rb.Push(ResultSuccess);
1414}
1415
1416void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
1417 LOG_WARNING(Service_AM, "(STUBBED) called");
1418
1419 system.Exit();
1420
1421 IPC::ResponseBuilder rb{ctx, 2};
1422 rb.Push(ResultSuccess);
1423}
1424
1425void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1426 struct LibraryAppletInfo {
1427 Applets::AppletId applet_id;
1428 Applets::LibraryAppletMode library_applet_mode;
1429 };
1430
1431 LOG_WARNING(Service_AM, "(STUBBED) called");
1432
1433 const LibraryAppletInfo applet_info{
1434 .applet_id = Applets::AppletId::MiiEdit,
1435 .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
1436 };
1437
1438 IPC::ResponseBuilder rb{ctx, 4};
1439 rb.Push(ResultSuccess);
1440 rb.PushRaw(applet_info);
1441}
1442
1443void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
1444 struct AppletIdentityInfo {
1445 Applets::AppletId applet_id;
1446 INSERT_PADDING_BYTES(0x4);
1447 u64 application_id;
1448 };
1449
1450 LOG_WARNING(Service_AM, "(STUBBED) called");
1451
1452 const AppletIdentityInfo applet_info{
1453 .applet_id = Applets::AppletId::QLaunch,
1454 .application_id = 0x0100000000001000ull,
1455 };
1456
1457 IPC::ResponseBuilder rb{ctx, 6};
1458 rb.Push(ResultSuccess);
1459 rb.PushRaw(applet_info);
1460}
1461
1462void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
1463 struct MiiEditV3 {
1464 Applets::MiiEditAppletInputCommon common;
1465 Applets::MiiEditAppletInputV3 input;
1466 };
1467 static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
1468
1469 MiiEditV3 mii_arguments{
1470 .common =
1471 {
1472 .version = Applets::MiiEditAppletVersion::Version3,
1473 .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
1474 },
1475 .input{},
1476 };
1477
1478 std::vector<u8> argument_data(sizeof(mii_arguments));
1479 std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
1480
1481 queue_data.emplace_back(std::move(argument_data));
1482}
1483
1484IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
1485 : ServiceFramework{system_, "IAppletCommonFunctions"} {
1486 // clang-format off
1487 static const FunctionInfo functions[] = {
1488 {0, nullptr, "SetTerminateResult"},
1489 {10, nullptr, "ReadThemeStorage"},
1490 {11, nullptr, "WriteThemeStorage"},
1491 {20, nullptr, "PushToAppletBoundChannel"},
1492 {21, nullptr, "TryPopFromAppletBoundChannel"},
1493 {40, nullptr, "GetDisplayLogicalResolution"},
1494 {42, nullptr, "SetDisplayMagnification"},
1495 {50, nullptr, "SetHomeButtonDoubleClickEnabled"},
1496 {51, nullptr, "GetHomeButtonDoubleClickEnabled"},
1497 {52, nullptr, "IsHomeButtonShortPressedBlocked"},
1498 {60, nullptr, "IsVrModeCurtainRequired"},
1499 {61, nullptr, "IsSleepRequiredByHighTemperature"},
1500 {62, nullptr, "IsSleepRequiredByLowBattery"},
1501 {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
1502 {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
1503 {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
1504 {90, nullptr, "OpenNamedChannelAsParent"},
1505 {91, nullptr, "OpenNamedChannelAsChild"},
1506 {100, nullptr, "SetApplicationCoreUsageMode"},
1507 };
1508 // clang-format on
1509
1510 RegisterHandlers(functions);
1511}
1512
1513IAppletCommonFunctions::~IAppletCommonFunctions() = default;
1514
1515void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
1516 LOG_WARNING(Service_AM, "(STUBBED) called");
1517
1518 IPC::ResponseBuilder rb{ctx, 2};
1519 rb.Push(ResultSuccess);
1520}
1368 1521
1369IApplicationFunctions::IApplicationFunctions(Core::System& system_) 1522IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1370 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, 1523 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@@ -1386,7 +1539,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, 1539 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, 1540 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, 1541 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
1389 {28, nullptr, "GetSaveDataSizeMax"}, 1542 {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
1390 {29, nullptr, "GetCacheStorageMax"}, 1543 {29, nullptr, "GetCacheStorageMax"},
1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, 1544 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, 1545 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1821,6 +1974,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
1821 rb.PushRaw(resp); 1974 rb.PushRaw(resp);
1822} 1975}
1823 1976
1977void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
1978 LOG_WARNING(Service_AM, "(STUBBED) called");
1979
1980 constexpr u64 size_max_normal = 0xFFFFFFF;
1981 constexpr u64 size_max_journal = 0xFFFFFFF;
1982
1983 IPC::ResponseBuilder rb{ctx, 6};
1984 rb.Push(ResultSuccess);
1985 rb.Push(size_max_normal);
1986 rb.Push(size_max_journal);
1987}
1988
1824void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1989void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called"); 1990 LOG_WARNING(Service_AM, "(STUBBED) called");
1826 1991
@@ -1929,9 +2094,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
1929 2094
1930void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { 2095void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
1931 auto message_queue = std::make_shared<AppletMessageQueue>(system); 2096 auto message_queue = std::make_shared<AppletMessageQueue>(system);
1932 // Needed on game boot
1933 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
1934
1935 auto server_manager = std::make_unique<ServerManager>(system); 2097 auto server_manager = std::make_unique<ServerManager>(system);
1936 2098
1937 server_manager->RegisterNamedService( 2099 server_manager->RegisterNamedService(
@@ -2037,8 +2199,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2037 : ServiceFramework{system_, "IProcessWindingController"} { 2199 : ServiceFramework{system_, "IProcessWindingController"} {
2038 // clang-format off 2200 // clang-format off
2039 static const FunctionInfo functions[] = { 2201 static const FunctionInfo functions[] = {
2040 {0, nullptr, "GetLaunchReason"}, 2202 {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
2041 {11, nullptr, "OpenCallingLibraryApplet"}, 2203 {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
2042 {21, nullptr, "PushContext"}, 2204 {21, nullptr, "PushContext"},
2043 {22, nullptr, "PopContext"}, 2205 {22, nullptr, "PopContext"},
2044 {23, nullptr, "CancelWindingReservation"}, 2206 {23, nullptr, "CancelWindingReservation"},
@@ -2052,4 +2214,46 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2052} 2214}
2053 2215
2054IProcessWindingController::~IProcessWindingController() = default; 2216IProcessWindingController::~IProcessWindingController() = default;
2217
2218void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
2219 LOG_WARNING(Service_AM, "(STUBBED) called");
2220
2221 struct AppletProcessLaunchReason {
2222 u8 flag;
2223 INSERT_PADDING_BYTES(3);
2224 };
2225 static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
2226 "AppletProcessLaunchReason is an invalid size");
2227
2228 AppletProcessLaunchReason reason{
2229 .flag = 0,
2230 };
2231
2232 IPC::ResponseBuilder rb{ctx, 3};
2233 rb.Push(ResultSuccess);
2234 rb.PushRaw(reason);
2235}
2236
2237void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
2238 const auto applet_id = Applets::AppletId::MiiEdit;
2239 const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
2240
2241 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
2242 applet_mode);
2243
2244 const auto& applet_manager{system.GetAppletManager()};
2245 const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
2246
2247 if (applet == nullptr) {
2248 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
2249
2250 IPC::ResponseBuilder rb{ctx, 2};
2251 rb.Push(ResultUnknown);
2252 return;
2253 }
2254
2255 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
2256 rb.Push(ResultSuccess);
2257 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
2258}
2055} // namespace Service::AM 2259} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f75a665b2..5b97eb5e3 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -120,6 +120,9 @@ class IDisplayController final : public ServiceFramework<IDisplayController> {
120public: 120public:
121 explicit IDisplayController(Core::System& system_); 121 explicit IDisplayController(Core::System& system_);
122 ~IDisplayController() override; 122 ~IDisplayController() override;
123
124private:
125 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
123}; 126};
124 127
125class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { 128class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -212,6 +215,11 @@ private:
212 CaptureButtonLongPressing, 215 CaptureButtonLongPressing,
213 }; 216 };
214 217
218 enum class SysPlatformRegion : s32 {
219 Global = 1,
220 Terra = 2,
221 };
222
215 void GetEventHandle(HLERequestContext& ctx); 223 void GetEventHandle(HLERequestContext& ctx);
216 void ReceiveMessage(HLERequestContext& ctx); 224 void ReceiveMessage(HLERequestContext& ctx);
217 void GetCurrentFocusState(HLERequestContext& ctx); 225 void GetCurrentFocusState(HLERequestContext& ctx);
@@ -227,6 +235,7 @@ private:
227 void GetDefaultDisplayResolution(HLERequestContext& ctx); 235 void GetDefaultDisplayResolution(HLERequestContext& ctx);
228 void SetCpuBoostMode(HLERequestContext& ctx); 236 void SetCpuBoostMode(HLERequestContext& ctx);
229 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 237 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
238 void GetSettingsPlatformRegion(HLERequestContext& ctx);
230 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 239 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
231 240
232 std::shared_ptr<AppletMessageQueue> msg_queue; 241 std::shared_ptr<AppletMessageQueue> msg_queue;
@@ -294,6 +303,26 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
294public: 303public:
295 explicit ILibraryAppletSelfAccessor(Core::System& system_); 304 explicit ILibraryAppletSelfAccessor(Core::System& system_);
296 ~ILibraryAppletSelfAccessor() override; 305 ~ILibraryAppletSelfAccessor() override;
306
307private:
308 void PopInData(HLERequestContext& ctx);
309 void PushOutData(HLERequestContext& ctx);
310 void GetLibraryAppletInfo(HLERequestContext& ctx);
311 void ExitProcessAndReturn(HLERequestContext& ctx);
312 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
313
314 void PushInShowMiiEditData();
315
316 std::deque<std::vector<u8>> queue_data;
317};
318
319class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
320public:
321 explicit IAppletCommonFunctions(Core::System& system_);
322 ~IAppletCommonFunctions() override;
323
324private:
325 void SetCpuBoostRequestPriority(HLERequestContext& ctx);
297}; 326};
298 327
299class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 328class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -316,6 +345,7 @@ private:
316 void ExtendSaveData(HLERequestContext& ctx); 345 void ExtendSaveData(HLERequestContext& ctx);
317 void GetSaveDataSize(HLERequestContext& ctx); 346 void GetSaveDataSize(HLERequestContext& ctx);
318 void CreateCacheStorage(HLERequestContext& ctx); 347 void CreateCacheStorage(HLERequestContext& ctx);
348 void GetSaveDataSizeMax(HLERequestContext& ctx);
319 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 349 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
320 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 350 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
321 void BeginBlockingHomeButton(HLERequestContext& ctx); 351 void BeginBlockingHomeButton(HLERequestContext& ctx);
@@ -377,6 +407,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
377public: 407public:
378 explicit IProcessWindingController(Core::System& system_); 408 explicit IProcessWindingController(Core::System& system_);
379 ~IProcessWindingController() override; 409 ~IProcessWindingController() override;
410
411private:
412 void GetLaunchReason(HLERequestContext& ctx);
413 void OpenCallingLibraryApplet(HLERequestContext& ctx);
380}; 414};
381 415
382void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); 416void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index ee9d99a54..eb12312cc 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -27,7 +27,7 @@ public:
27 {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, 27 {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, 28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, 29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
30 {21, nullptr, "GetAppletCommonFunctions"}, 30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
31 {22, nullptr, "GetHomeMenuFunctions"}, 31 {22, nullptr, "GetHomeMenuFunctions"},
32 {23, nullptr, "GetGlobalStateController"}, 32 {23, nullptr, "GetGlobalStateController"},
33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
@@ -86,28 +86,36 @@ private:
86 rb.PushIpcInterface<IProcessWindingController>(system); 86 rb.PushIpcInterface<IProcessWindingController>(system);
87 } 87 }
88 88
89 void GetDebugFunctions(HLERequestContext& ctx) { 89 void GetLibraryAppletCreator(HLERequestContext& ctx) {
90 LOG_DEBUG(Service_AM, "called"); 90 LOG_DEBUG(Service_AM, "called");
91 91
92 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 92 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
93 rb.Push(ResultSuccess); 93 rb.Push(ResultSuccess);
94 rb.PushIpcInterface<IDebugFunctions>(system); 94 rb.PushIpcInterface<ILibraryAppletCreator>(system);
95 } 95 }
96 96
97 void GetLibraryAppletCreator(HLERequestContext& ctx) { 97 void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
98 LOG_DEBUG(Service_AM, "called"); 98 LOG_DEBUG(Service_AM, "called");
99 99
100 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 100 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
101 rb.Push(ResultSuccess); 101 rb.Push(ResultSuccess);
102 rb.PushIpcInterface<ILibraryAppletCreator>(system); 102 rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
103 } 103 }
104 104
105 void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { 105 void GetAppletCommonFunctions(HLERequestContext& ctx) {
106 LOG_DEBUG(Service_AM, "called"); 106 LOG_DEBUG(Service_AM, "called");
107 107
108 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 108 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
109 rb.Push(ResultSuccess); 109 rb.Push(ResultSuccess);
110 rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); 110 rb.PushIpcInterface<IAppletCommonFunctions>(system);
111 }
112
113 void GetDebugFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called");
115
116 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
117 rb.Push(ResultSuccess);
118 rb.PushIpcInterface<IDebugFunctions>(system);
111 } 119 }
112 120
113 Nvnflinger::Nvnflinger& nvnflinger; 121 Nvnflinger::Nvnflinger& nvnflinger;
@@ -133,7 +141,7 @@ public:
133 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, 141 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
134 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, 142 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
135 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, 143 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
136 {23, nullptr, "GetAppletCommonFunctions"}, 144 {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
137 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 145 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
138 }; 146 };
139 // clang-format on 147 // clang-format on
@@ -182,14 +190,6 @@ private:
182 rb.PushIpcInterface<IDisplayController>(system); 190 rb.PushIpcInterface<IDisplayController>(system);
183 } 191 }
184 192
185 void GetDebugFunctions(HLERequestContext& ctx) {
186 LOG_DEBUG(Service_AM, "called");
187
188 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
189 rb.Push(ResultSuccess);
190 rb.PushIpcInterface<IDebugFunctions>(system);
191 }
192
193 void GetLibraryAppletCreator(HLERequestContext& ctx) { 193 void GetLibraryAppletCreator(HLERequestContext& ctx) {
194 LOG_DEBUG(Service_AM, "called"); 194 LOG_DEBUG(Service_AM, "called");
195 195
@@ -222,6 +222,22 @@ private:
222 rb.PushIpcInterface<IApplicationCreator>(system); 222 rb.PushIpcInterface<IApplicationCreator>(system);
223 } 223 }
224 224
225 void GetAppletCommonFunctions(HLERequestContext& ctx) {
226 LOG_DEBUG(Service_AM, "called");
227
228 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
229 rb.Push(ResultSuccess);
230 rb.PushIpcInterface<IAppletCommonFunctions>(system);
231 }
232
233 void GetDebugFunctions(HLERequestContext& ctx) {
234 LOG_DEBUG(Service_AM, "called");
235
236 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
237 rb.Push(ResultSuccess);
238 rb.PushIpcInterface<IDebugFunctions>(system);
239 }
240
225 Nvnflinger::Nvnflinger& nvnflinger; 241 Nvnflinger::Nvnflinger& nvnflinger;
226 std::shared_ptr<AppletMessageQueue> msg_queue; 242 std::shared_ptr<AppletMessageQueue> msg_queue;
227}; 243};
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
index 350a90818..50adc7c02 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.cpp
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -7,7 +7,9 @@
7#include "core/frontend/applets/mii_edit.h" 7#include "core/frontend/applets/mii_edit.h"
8#include "core/hle/service/am/am.h" 8#include "core/hle/service/am/am.h"
9#include "core/hle/service/am/applets/applet_mii_edit.h" 9#include "core/hle/service/am/applets/applet_mii_edit.h"
10#include "core/hle/service/mii/mii.h"
10#include "core/hle/service/mii/mii_manager.h" 11#include "core/hle/service/mii/mii_manager.h"
12#include "core/hle/service/sm/sm.h"
11 13
12namespace Service::AM::Applets { 14namespace Service::AM::Applets {
13 15
@@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
56 sizeof(MiiEditAppletInputV4)); 58 sizeof(MiiEditAppletInputV4));
57 break; 59 break;
58 } 60 }
61
62 manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
63 if (manager == nullptr) {
64 manager = std::make_shared<Mii::MiiManager>();
65 }
66 manager->Initialize(metadata);
59} 67}
60 68
61bool MiiEdit::TransactionComplete() const { 69bool MiiEdit::TransactionComplete() const {
@@ -78,22 +86,46 @@ void MiiEdit::Execute() {
78 // This is a default stub for each of the MiiEdit applet modes. 86 // This is a default stub for each of the MiiEdit applet modes.
79 switch (applet_input_common.applet_mode) { 87 switch (applet_input_common.applet_mode) {
80 case MiiEditAppletMode::ShowMiiEdit: 88 case MiiEditAppletMode::ShowMiiEdit:
81 case MiiEditAppletMode::AppendMii:
82 case MiiEditAppletMode::AppendMiiImage: 89 case MiiEditAppletMode::AppendMiiImage:
83 case MiiEditAppletMode::UpdateMiiImage: 90 case MiiEditAppletMode::UpdateMiiImage:
84 MiiEditOutput(MiiEditResult::Success, 0); 91 MiiEditOutput(MiiEditResult::Success, 0);
85 break; 92 break;
86 case MiiEditAppletMode::CreateMii: 93 case MiiEditAppletMode::AppendMii: {
87 case MiiEditAppletMode::EditMii: {
88 Mii::CharInfo char_info{};
89 Mii::StoreData store_data{}; 94 Mii::StoreData store_data{};
90 store_data.BuildBase(Mii::Gender::Male); 95 store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
91 char_info.SetFromStoreData(store_data); 96 store_data.SetNickname({u'y', u'u', u'z', u'u'});
97 store_data.SetChecksum();
98 const auto result = manager->AddOrReplace(metadata, store_data);
99
100 if (result.IsError()) {
101 MiiEditOutput(MiiEditResult::Cancel, 0);
102 break;
103 }
104
105 s32 index = manager->FindIndex(store_data.GetCreateId(), false);
106
107 if (index == -1) {
108 MiiEditOutput(MiiEditResult::Cancel, 0);
109 break;
110 }
111
112 MiiEditOutput(MiiEditResult::Success, index);
113 break;
114 }
115 case MiiEditAppletMode::CreateMii: {
116 Mii::CharInfo char_info{};
117 manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
92 118
93 const MiiEditCharInfo edit_char_info{ 119 const MiiEditCharInfo edit_char_info{
94 .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii 120 .mii_info{char_info},
95 ? applet_input_v4.char_info.mii_info 121 };
96 : char_info}, 122
123 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
124 break;
125 }
126 case MiiEditAppletMode::EditMii: {
127 const MiiEditCharInfo edit_char_info{
128 .mii_info{applet_input_v4.char_info.mii_info},
97 }; 129 };
98 130
99 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); 131 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
@@ -113,6 +145,8 @@ void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) {
113 .index{index}, 145 .index{index},
114 }; 146 };
115 147
148 LOG_INFO(Input, "called, result={}, index={}", result, index);
149
116 std::vector<u8> out_data(sizeof(MiiEditAppletOutput)); 150 std::vector<u8> out_data(sizeof(MiiEditAppletOutput));
117 std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput)); 151 std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput));
118 152
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h
index 3f46fae1b..7ff34af49 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -11,6 +11,11 @@ namespace Core {
11class System; 11class System;
12} // namespace Core 12} // namespace Core
13 13
14namespace Service::Mii {
15struct DatabaseSessionMetadata;
16class MiiManager;
17} // namespace Service::Mii
18
14namespace Service::AM::Applets { 19namespace Service::AM::Applets {
15 20
16class MiiEdit final : public Applet { 21class MiiEdit final : public Applet {
@@ -40,6 +45,8 @@ private:
40 MiiEditAppletInputV4 applet_input_v4{}; 45 MiiEditAppletInputV4 applet_input_v4{};
41 46
42 bool is_complete{false}; 47 bool is_complete{false};
48 std::shared_ptr<Mii::MiiManager> manager = nullptr;
49 Mii::DatabaseSessionMetadata metadata{};
43}; 50};
44 51
45} // namespace Service::AM::Applets 52} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 38c2138e8..7075ab800 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -22,6 +22,8 @@
22 22
23namespace Service::AOC { 23namespace Service::AOC {
24 24
25constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400};
26
25static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { 27static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
26 return FileSys::GetBaseTitleID(title_id) == base; 28 return FileSys::GetBaseTitleID(title_id) == base;
27} 29}
@@ -54,8 +56,8 @@ public:
54 {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, 56 {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
55 {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, 57 {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
56 {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, 58 {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
57 {3, nullptr, "PopPurchasedProductInfo"}, 59 {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"},
58 {4, nullptr, "PopPurchasedProductInfoWithUid"}, 60 {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"},
59 }; 61 };
60 // clang-format on 62 // clang-format on
61 63
@@ -101,6 +103,20 @@ private:
101 rb.PushCopyObjects(purchased_event->GetReadableEvent()); 103 rb.PushCopyObjects(purchased_event->GetReadableEvent());
102 } 104 }
103 105
106 void PopPurchasedProductInfo(HLERequestContext& ctx) {
107 LOG_DEBUG(Service_AOC, "(STUBBED) called");
108
109 IPC::ResponseBuilder rb{ctx, 2};
110 rb.Push(ResultNoPurchasedProductInfoAvailable);
111 }
112
113 void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AOC, "(STUBBED) called");
115
116 IPC::ResponseBuilder rb{ctx, 2};
117 rb.Push(ResultNoPurchasedProductInfoAvailable);
118 }
119
104 KernelHelpers::ServiceContext service_context; 120 KernelHelpers::ServiceContext service_context;
105 121
106 Kernel::KEvent* purchased_event; 122 Kernel::KEvent* purchased_event;
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h
index 3d3d3d97a..c41345f7e 100644
--- a/src/core/hle/service/audio/errors.h
+++ b/src/core/hle/service/audio/errors.h
@@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513};
20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; 20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; 21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
22 22
23constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
24constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
25constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
26constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
27constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
28constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
29constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
30constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
31constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
32constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
33constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
34
23} // namespace Service::Audio 35} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 1557e6088..6a7bf9416 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -1,420 +1,506 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 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 <chrono>
5#include <cstring>
6#include <memory> 4#include <memory>
7#include <vector> 5#include <vector>
8 6
9#include <opus.h> 7#include "audio_core/opus/decoder.h"
10#include <opus_multistream.h> 8#include "audio_core/opus/parameters.h"
11
12#include "common/assert.h" 9#include "common/assert.h"
13#include "common/logging/log.h" 10#include "common/logging/log.h"
14#include "common/scratch_buffer.h" 11#include "common/scratch_buffer.h"
12#include "core/core.h"
15#include "core/hle/service/audio/hwopus.h" 13#include "core/hle/service/audio/hwopus.h"
16#include "core/hle/service/ipc_helpers.h" 14#include "core/hle/service/ipc_helpers.h"
17 15
18namespace Service::Audio { 16namespace Service::Audio {
19namespace { 17using namespace AudioCore::OpusDecoder;
20struct OpusDeleter { 18
21 void operator()(OpusMSDecoder* ptr) const { 19class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
22 opus_multistream_decoder_destroy(ptr); 20public:
21 explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus)
22 : ServiceFramework{system_, "IHardwareOpusDecoder"},
23 impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} {
24 // clang-format off
25 static const FunctionInfo functions[] = {
26 {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"},
27 {1, &IHardwareOpusDecoder::SetContext, "SetContext"},
28 {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"},
29 {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"},
30 {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
31 {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"},
32 {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"},
33 {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
34 {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"},
35 {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
36 };
37 // clang-format on
38
39 RegisterHandlers(functions);
23 } 40 }
24};
25 41
26using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; 42 Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
43 u64 transfer_memory_size) {
44 return impl->Initialize(params, transfer_memory, transfer_memory_size);
45 }
27 46
28struct OpusPacketHeader { 47 Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
29 // Packet size in bytes. 48 u64 transfer_memory_size) {
30 u32_be size; 49 return impl->Initialize(params, transfer_memory, transfer_memory_size);
31 // Indicates the final range of the codec's entropy coder. 50 }
32 u32_be final_range;
33};
34static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
35 51
36class OpusDecoderState { 52private:
37public: 53 void DecodeInterleavedOld(HLERequestContext& ctx) {
38 /// Describes extra behavior that may be asked of the decoding context. 54 IPC::RequestParser rp{ctx};
39 enum class ExtraBehavior {
40 /// No extra behavior.
41 None,
42 55
43 /// Resets the decoder context back to a freshly initialized state. 56 auto input_data{ctx.ReadBuffer(0)};
44 ResetContext, 57 output_data.resize_destructive(ctx.GetWriteBufferSize());
45 };
46 58
47 enum class PerfTime { 59 u32 size{};
48 Disabled, 60 u32 sample_count{};
49 Enabled, 61 auto result =
50 }; 62 impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false);
51 63
52 explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) 64 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
53 : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} 65
54 66 ctx.WriteBuffer(output_data);
55 // Decodes interleaved Opus packets. Optionally allows reporting time taken to 67
56 // perform the decoding, as well as any relevant extra behavior. 68 IPC::ResponseBuilder rb{ctx, 4};
57 void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, 69 rb.Push(result);
58 ExtraBehavior extra_behavior) { 70 rb.Push(size);
59 if (perf_time == PerfTime::Disabled) { 71 rb.Push(sample_count);
60 DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
61 } else {
62 u64 performance = 0;
63 DecodeInterleavedHelper(ctx, &performance, extra_behavior);
64 }
65 } 72 }
66 73
67private: 74 void SetContext(HLERequestContext& ctx) {
68 void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, 75 IPC::RequestParser rp{ctx};
69 ExtraBehavior extra_behavior) { 76
70 u32 consumed = 0; 77 LOG_DEBUG(Service_Audio, "called");
71 u32 sample_count = 0; 78
72 samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); 79 auto input_data{ctx.ReadBuffer(0)};
73 80 auto result = impl->SetContext(input_data);
74 if (extra_behavior == ExtraBehavior::ResetContext) { 81
75 ResetDecoderContext(); 82 IPC::ResponseBuilder rb{ctx, 2};
76 } 83 rb.Push(result);
77 84 }
78 if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { 85
79 LOG_ERROR(Audio, "Failed to decode opus data"); 86 void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) {
80 IPC::ResponseBuilder rb{ctx, 2}; 87 IPC::RequestParser rp{ctx};
81 // TODO(ogniK): Use correct error code 88
82 rb.Push(ResultUnknown); 89 auto input_data{ctx.ReadBuffer(0)};
83 return; 90 output_data.resize_destructive(ctx.GetWriteBufferSize());
84 } 91
85 92 u32 size{};
86 const u32 param_size = performance != nullptr ? 6 : 4; 93 u32 sample_count{};
87 IPC::ResponseBuilder rb{ctx, param_size}; 94 auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count,
88 rb.Push(ResultSuccess); 95 input_data, output_data, false);
89 rb.Push<u32>(consumed); 96
90 rb.Push<u32>(sample_count); 97 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
91 if (performance) { 98
92 rb.Push<u64>(*performance); 99 ctx.WriteBuffer(output_data);
93 } 100
94 ctx.WriteBuffer(samples); 101 IPC::ResponseBuilder rb{ctx, 4};
102 rb.Push(result);
103 rb.Push(size);
104 rb.Push(sample_count);
95 } 105 }
96 106
97 bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, 107 void SetContextForMultiStream(HLERequestContext& ctx) {
98 std::span<opus_int16> output, u64* out_performance_time) const { 108 IPC::RequestParser rp{ctx};
99 const auto start_time = std::chrono::steady_clock::now(); 109
100 const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); 110 LOG_DEBUG(Service_Audio, "called");
101 if (sizeof(OpusPacketHeader) > input.size()) { 111
102 LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", 112 auto input_data{ctx.ReadBuffer(0)};
103 sizeof(OpusPacketHeader), input.size()); 113 auto result = impl->SetContext(input_data);
104 return false; 114
105 } 115 IPC::ResponseBuilder rb{ctx, 2};
106 116 rb.Push(result);
107 OpusPacketHeader hdr{};
108 std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
109 if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
110 LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
111 sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
112 return false;
113 }
114
115 const auto frame = input.data() + sizeof(OpusPacketHeader);
116 const auto decoded_sample_count = opus_packet_get_nb_samples(
117 frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
118 static_cast<opus_int32>(sample_rate));
119 if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
120 LOG_ERROR(
121 Audio,
122 "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
123 decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
124 return false;
125 }
126
127 const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
128 const auto out_sample_count =
129 opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
130 if (out_sample_count < 0) {
131 LOG_ERROR(Audio,
132 "Incorrect sample count received from opus_decode, "
133 "output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
134 out_sample_count, frame_size, static_cast<u32>(hdr.size));
135 return false;
136 }
137
138 const auto end_time = std::chrono::steady_clock::now() - start_time;
139 sample_count = out_sample_count;
140 consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
141 if (out_performance_time != nullptr) {
142 *out_performance_time =
143 std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
144 }
145
146 return true;
147 } 117 }
148 118
149 void ResetDecoderContext() { 119 void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
150 ASSERT(decoder != nullptr); 120 IPC::RequestParser rp{ctx};
121
122 auto input_data{ctx.ReadBuffer(0)};
123 output_data.resize_destructive(ctx.GetWriteBufferSize());
124
125 u32 size{};
126 u32 sample_count{};
127 u64 time_taken{};
128 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
129 output_data, false);
130
131 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
132 sample_count, time_taken);
151 133
152 opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); 134 ctx.WriteBuffer(output_data);
135
136 IPC::ResponseBuilder rb{ctx, 6};
137 rb.Push(result);
138 rb.Push(size);
139 rb.Push(sample_count);
140 rb.Push(time_taken);
153 } 141 }
154 142
155 OpusDecoderPtr decoder; 143 void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) {
156 u32 sample_rate; 144 IPC::RequestParser rp{ctx};
157 u32 channel_count;
158 Common::ScratchBuffer<opus_int16> samples;
159};
160 145
161class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { 146 auto input_data{ctx.ReadBuffer(0)};
162public: 147 output_data.resize_destructive(ctx.GetWriteBufferSize());
163 explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_)
164 : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
165 std::move(decoder_state_)} {
166 // clang-format off
167 static const FunctionInfo functions[] = {
168 {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
169 {1, nullptr, "SetContext"},
170 {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
171 {3, nullptr, "SetContextForMultiStream"},
172 {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
173 {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
174 {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
175 {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
176 {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
177 {9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
178 };
179 // clang-format on
180 148
181 RegisterHandlers(functions); 149 u32 size{};
150 u32 sample_count{};
151 u64 time_taken{};
152 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
153 input_data, output_data, false);
154
155 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
156 sample_count, time_taken);
157
158 ctx.WriteBuffer(output_data);
159
160 IPC::ResponseBuilder rb{ctx, 6};
161 rb.Push(result);
162 rb.Push(size);
163 rb.Push(sample_count);
164 rb.Push(time_taken);
182 } 165 }
183 166
184private: 167 void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) {
185 void DecodeInterleavedOld(HLERequestContext& ctx) { 168 IPC::RequestParser rp{ctx};
186 LOG_DEBUG(Audio, "called"); 169
170 auto reset{rp.Pop<bool>()};
171
172 auto input_data{ctx.ReadBuffer(0)};
173 output_data.resize_destructive(ctx.GetWriteBufferSize());
174
175 u32 size{};
176 u32 sample_count{};
177 u64 time_taken{};
178 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
179 output_data, reset);
180
181 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
182 reset, size, sample_count, time_taken);
183
184 ctx.WriteBuffer(output_data);
187 185
188 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, 186 IPC::ResponseBuilder rb{ctx, 6};
189 OpusDecoderState::ExtraBehavior::None); 187 rb.Push(result);
188 rb.Push(size);
189 rb.Push(sample_count);
190 rb.Push(time_taken);
190 } 191 }
191 192
192 void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { 193 void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) {
193 LOG_DEBUG(Audio, "called"); 194 IPC::RequestParser rp{ctx};
195
196 auto reset{rp.Pop<bool>()};
197
198 auto input_data{ctx.ReadBuffer(0)};
199 output_data.resize_destructive(ctx.GetWriteBufferSize());
200
201 u32 size{};
202 u32 sample_count{};
203 u64 time_taken{};
204 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
205 input_data, output_data, reset);
194 206
195 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, 207 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
196 OpusDecoderState::ExtraBehavior::None); 208 reset, size, sample_count, time_taken);
209
210 ctx.WriteBuffer(output_data);
211
212 IPC::ResponseBuilder rb{ctx, 6};
213 rb.Push(result);
214 rb.Push(size);
215 rb.Push(sample_count);
216 rb.Push(time_taken);
197 } 217 }
198 218
199 void DecodeInterleaved(HLERequestContext& ctx) { 219 void DecodeInterleaved(HLERequestContext& ctx) {
200 LOG_DEBUG(Audio, "called");
201
202 IPC::RequestParser rp{ctx}; 220 IPC::RequestParser rp{ctx};
203 const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
204 : OpusDecoderState::ExtraBehavior::None;
205 221
206 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); 222 auto reset{rp.Pop<bool>()};
223
224 auto input_data{ctx.ReadBuffer(0)};
225 output_data.resize_destructive(ctx.GetWriteBufferSize());
226
227 u32 size{};
228 u32 sample_count{};
229 u64 time_taken{};
230 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
231 output_data, reset);
232
233 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
234 reset, size, sample_count, time_taken);
235
236 ctx.WriteBuffer(output_data);
237
238 IPC::ResponseBuilder rb{ctx, 6};
239 rb.Push(result);
240 rb.Push(size);
241 rb.Push(sample_count);
242 rb.Push(time_taken);
207 } 243 }
208 244
209 void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { 245 void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
210 LOG_DEBUG(Audio, "called");
211
212 IPC::RequestParser rp{ctx}; 246 IPC::RequestParser rp{ctx};
213 const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
214 : OpusDecoderState::ExtraBehavior::None;
215 247
216 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); 248 auto reset{rp.Pop<bool>()};
249
250 auto input_data{ctx.ReadBuffer(0)};
251 output_data.resize_destructive(ctx.GetWriteBufferSize());
252
253 u32 size{};
254 u32 sample_count{};
255 u64 time_taken{};
256 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
257 input_data, output_data, reset);
258
259 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
260 reset, size, sample_count, time_taken);
261
262 ctx.WriteBuffer(output_data);
263
264 IPC::ResponseBuilder rb{ctx, 6};
265 rb.Push(result);
266 rb.Push(size);
267 rb.Push(sample_count);
268 rb.Push(time_taken);
217 } 269 }
218 270
219 OpusDecoderState decoder_state; 271 std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl;
272 Common::ScratchBuffer<u8> output_data;
220}; 273};
221 274
222std::size_t WorkerBufferSize(u32 channel_count) { 275void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
223 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); 276 IPC::RequestParser rp{ctx};
224 constexpr int num_streams = 1;
225 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
226 return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
227}
228 277
229// Creates the mapping table that maps the input channels to the particular 278 auto params = rp.PopRaw<OpusParameters>();
230// output channels. In the stereo case, we map the left and right input channels 279 auto transfer_memory_size{rp.Pop<u32>()};
231// to the left and right output channels respectively. 280 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
232// 281 auto transfer_memory{
233// However, in the monophonic case, we only map the one available channel 282 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
234// to the sole output channel. We specify 255 for the would-be right channel 283 transfer_memory_handle)};
235// as this is a special value defined by Opus to indicate to the decoder to 284
236// ignore that channel. 285 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
237std::array<u8, 2> CreateMappingTable(u32 channel_count) { 286 params.sample_rate, params.channel_count, transfer_memory_size);
238 if (channel_count == 2) {
239 return {{0, 1}};
240 }
241 287
242 return {{0, 255}}; 288 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
289
290 OpusParametersEx ex{
291 .sample_rate = params.sample_rate,
292 .channel_count = params.channel_count,
293 .use_large_frame_size = false,
294 };
295 auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
296
297 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
298 rb.Push(result);
299 rb.PushIpcInterface(decoder);
243} 300}
244} // Anonymous namespace
245 301
246void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { 302void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
247 IPC::RequestParser rp{ctx}; 303 IPC::RequestParser rp{ctx};
248 const auto sample_rate = rp.Pop<u32>(); 304 auto params = rp.PopRaw<OpusParameters>();
249 const auto channel_count = rp.Pop<u32>();
250 305
251 LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); 306 u64 size{};
307 auto result = impl.GetWorkBufferSize(params, size);
252 308
253 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 309 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}",
254 sample_rate == 12000 || sample_rate == 8000, 310 params.sample_rate, params.channel_count, size);
255 "Invalid sample rate");
256 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
257 311
258 const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); 312 IPC::ResponseBuilder rb{ctx, 4};
259 LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); 313 rb.Push(result);
260 314 rb.Push(size);
261 IPC::ResponseBuilder rb{ctx, 3};
262 rb.Push(ResultSuccess);
263 rb.Push<u32>(worker_buffer_sz);
264} 315}
265 316
266void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { 317void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
267 GetWorkBufferSize(ctx); 318 IPC::RequestParser rp{ctx};
319
320 auto input{ctx.ReadBuffer()};
321 OpusMultiStreamParameters params;
322 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
323
324 auto transfer_memory_size{rp.Pop<u32>()};
325 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
326 auto transfer_memory{
327 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
328 transfer_memory_handle)};
329
330 LOG_DEBUG(Service_Audio,
331 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
332 "transfer_memory_size 0x{:X}",
333 params.sample_rate, params.channel_count, params.total_stream_count,
334 params.stereo_stream_count, transfer_memory_size);
335
336 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
337
338 OpusMultiStreamParametersEx ex{
339 .sample_rate = params.sample_rate,
340 .channel_count = params.channel_count,
341 .total_stream_count = params.total_stream_count,
342 .stereo_stream_count = params.stereo_stream_count,
343 .use_large_frame_size = false,
344 .mappings{},
345 };
346 std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings));
347 auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
348
349 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
350 rb.Push(result);
351 rb.PushIpcInterface(decoder);
268} 352}
269 353
270void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { 354void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
271 GetWorkBufferSizeEx(ctx); 355 IPC::RequestParser rp{ctx};
356
357 auto input{ctx.ReadBuffer()};
358 OpusMultiStreamParameters params;
359 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
360
361 u64 size{};
362 auto result = impl.GetWorkBufferSizeForMultiStream(params, size);
363
364 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
365
366 IPC::ResponseBuilder rb{ctx, 4};
367 rb.Push(result);
368 rb.Push(size);
272} 369}
273 370
274void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { 371void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
275 OpusMultiStreamParametersEx param; 372 IPC::RequestParser rp{ctx};
276 std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
277 373
278 const auto sample_rate = param.sample_rate; 374 auto params = rp.PopRaw<OpusParametersEx>();
279 const auto channel_count = param.channel_count; 375 auto transfer_memory_size{rp.Pop<u32>()};
280 const auto number_streams = param.number_streams; 376 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
281 const auto number_stereo_streams = param.number_stereo_streams; 377 auto transfer_memory{
378 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
379 transfer_memory_handle)};
282 380
283 LOG_DEBUG( 381 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
284 Audio, 382 params.sample_rate, params.channel_count, transfer_memory_size);
285 "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
286 sample_rate, channel_count, number_streams, number_stereo_streams);
287 383
288 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 384 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
289 sample_rate == 12000 || sample_rate == 8000,
290 "Invalid sample rate");
291 385
292 const u32 worker_buffer_sz = 386 auto result =
293 static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); 387 decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
294 388
295 IPC::ResponseBuilder rb{ctx, 3}; 389 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
296 rb.Push(ResultSuccess); 390 rb.Push(result);
297 rb.Push<u32>(worker_buffer_sz); 391 rb.PushIpcInterface(decoder);
298} 392}
299 393
300void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { 394void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
301 IPC::RequestParser rp{ctx}; 395 IPC::RequestParser rp{ctx};
302 const auto sample_rate = rp.Pop<u32>(); 396 auto params = rp.PopRaw<OpusParametersEx>();
303 const auto channel_count = rp.Pop<u32>();
304 const auto buffer_sz = rp.Pop<u32>();
305
306 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
307 channel_count, buffer_sz);
308
309 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
310 sample_rate == 12000 || sample_rate == 8000,
311 "Invalid sample rate");
312 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
313
314 const std::size_t worker_sz = WorkerBufferSize(channel_count);
315 ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
316
317 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
318 const auto mapping_table = CreateMappingTable(channel_count);
319
320 int error = 0;
321 OpusDecoderPtr decoder{
322 opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
323 num_stereo_streams, mapping_table.data(), &error)};
324 if (error != OPUS_OK || decoder == nullptr) {
325 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
326 IPC::ResponseBuilder rb{ctx, 2};
327 // TODO(ogniK): Use correct error code
328 rb.Push(ResultUnknown);
329 return;
330 }
331 397
332 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 398 u64 size{};
333 rb.Push(ResultSuccess); 399 auto result = impl.GetWorkBufferSizeEx(params, size);
334 rb.PushIpcInterface<IHardwareOpusDecoderManager>( 400
335 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); 401 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
402
403 IPC::ResponseBuilder rb{ctx, 4};
404 rb.Push(result);
405 rb.Push(size);
336} 406}
337 407
338void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { 408void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
339 IPC::RequestParser rp{ctx}; 409 IPC::RequestParser rp{ctx};
340 const auto sample_rate = rp.Pop<u32>();
341 const auto channel_count = rp.Pop<u32>();
342 410
343 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); 411 auto input{ctx.ReadBuffer()};
412 OpusMultiStreamParametersEx params;
413 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
414
415 auto transfer_memory_size{rp.Pop<u32>()};
416 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
417 auto transfer_memory{
418 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
419 transfer_memory_handle)};
344 420
345 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 421 LOG_DEBUG(Service_Audio,
346 sample_rate == 12000 || sample_rate == 8000, 422 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
347 "Invalid sample rate"); 423 "use_large_frame_size {}"
348 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); 424 "transfer_memory_size 0x{:X}",
425 params.sample_rate, params.channel_count, params.total_stream_count,
426 params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size);
349 427
350 const int num_stereo_streams = channel_count == 2 ? 1 : 0; 428 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
351 const auto mapping_table = CreateMappingTable(channel_count);
352 429
353 int error = 0; 430 auto result =
354 OpusDecoderPtr decoder{ 431 decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
355 opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
356 num_stereo_streams, mapping_table.data(), &error)};
357 if (error != OPUS_OK || decoder == nullptr) {
358 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
359 IPC::ResponseBuilder rb{ctx, 2};
360 // TODO(ogniK): Use correct error code
361 rb.Push(ResultUnknown);
362 return;
363 }
364 432
365 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 433 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
366 rb.Push(ResultSuccess); 434 rb.Push(result);
367 rb.PushIpcInterface<IHardwareOpusDecoderManager>( 435 rb.PushIpcInterface(decoder);
368 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
369} 436}
370 437
371void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { 438void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
439 IPC::RequestParser rp{ctx};
440
441 auto input{ctx.ReadBuffer()};
372 OpusMultiStreamParametersEx params; 442 OpusMultiStreamParametersEx params;
373 std::memcpy(&params, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); 443 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
374
375 const auto& sample_rate = params.sample_rate;
376 const auto& channel_count = params.channel_count;
377
378 LOG_INFO(
379 Audio,
380 "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
381 sample_rate, channel_count, params.number_streams, params.number_stereo_streams);
382
383 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
384 sample_rate == 12000 || sample_rate == 8000,
385 "Invalid sample rate");
386
387 int error = 0;
388 OpusDecoderPtr decoder{opus_multistream_decoder_create(
389 sample_rate, static_cast<int>(channel_count), params.number_streams,
390 params.number_stereo_streams, params.channel_mappings.data(), &error)};
391 if (error != OPUS_OK || decoder == nullptr) {
392 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
393 IPC::ResponseBuilder rb{ctx, 2};
394 // TODO(ogniK): Use correct error code
395 rb.Push(ResultUnknown);
396 return;
397 }
398 444
399 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 445 u64 size{};
400 rb.Push(ResultSuccess); 446 auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size);
401 rb.PushIpcInterface<IHardwareOpusDecoderManager>( 447
402 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); 448 LOG_DEBUG(Service_Audio,
449 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
450 "use_large_frame_size {} -- returned size 0x{:X}",
451 params.sample_rate, params.channel_count, params.total_stream_count,
452 params.stereo_stream_count, params.use_large_frame_size, size);
453
454 IPC::ResponseBuilder rb{ctx, 4};
455 rb.Push(result);
456 rb.Push(size);
457}
458
459void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
460 IPC::RequestParser rp{ctx};
461 auto params = rp.PopRaw<OpusParametersEx>();
462
463 u64 size{};
464 auto result = impl.GetWorkBufferSizeExEx(params, size);
465
466 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
467
468 IPC::ResponseBuilder rb{ctx, 4};
469 rb.Push(result);
470 rb.Push(size);
471}
472
473void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
474 IPC::RequestParser rp{ctx};
475
476 auto input{ctx.ReadBuffer()};
477 OpusMultiStreamParametersEx params;
478 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
479
480 u64 size{};
481 auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size);
482
483 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
484
485 IPC::ResponseBuilder rb{ctx, 4};
486 rb.Push(result);
487 rb.Push(size);
403} 488}
404 489
405HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { 490HwOpus::HwOpus(Core::System& system_)
491 : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} {
406 static const FunctionInfo functions[] = { 492 static const FunctionInfo functions[] = {
407 {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, 493 {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
408 {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, 494 {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
409 {2, nullptr, "OpenOpusDecoderForMultiStream"}, 495 {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"},
410 {3, nullptr, "GetWorkBufferSizeForMultiStream"}, 496 {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"},
411 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, 497 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
412 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, 498 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
413 {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, 499 {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
414 "OpenHardwareOpusDecoderForMultiStreamEx"}, 500 "OpenHardwareOpusDecoderForMultiStreamEx"},
415 {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, 501 {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
416 {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, 502 {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
417 {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, 503 {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"},
418 }; 504 };
419 RegisterHandlers(functions); 505 RegisterHandlers(functions);
420} 506}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index 90867bf74..d3960065e 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "audio_core/opus/decoder_manager.h"
6#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
7 8
8namespace Core { 9namespace Core {
@@ -11,18 +12,6 @@ class System;
11 12
12namespace Service::Audio { 13namespace Service::Audio {
13 14
14struct OpusMultiStreamParametersEx {
15 u32 sample_rate;
16 u32 channel_count;
17 u32 number_streams;
18 u32 number_stereo_streams;
19 u32 use_large_frame_size;
20 u32 padding;
21 std::array<u8, 0x100> channel_mappings;
22};
23static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
24 "OpusMultiStreamParametersEx has incorrect size");
25
26class HwOpus final : public ServiceFramework<HwOpus> { 15class HwOpus final : public ServiceFramework<HwOpus> {
27public: 16public:
28 explicit HwOpus(Core::System& system_); 17 explicit HwOpus(Core::System& system_);
@@ -30,12 +19,18 @@ public:
30 19
31private: 20private:
32 void OpenHardwareOpusDecoder(HLERequestContext& ctx); 21 void OpenHardwareOpusDecoder(HLERequestContext& ctx);
33 void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
34 void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
35 void GetWorkBufferSize(HLERequestContext& ctx); 22 void GetWorkBufferSize(HLERequestContext& ctx);
23 void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx);
24 void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx);
25 void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
36 void GetWorkBufferSizeEx(HLERequestContext& ctx); 26 void GetWorkBufferSizeEx(HLERequestContext& ctx);
37 void GetWorkBufferSizeExEx(HLERequestContext& ctx); 27 void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
38 void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); 28 void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
29 void GetWorkBufferSizeExEx(HLERequestContext& ctx);
30 void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx);
31
32 Core::System& system;
33 AudioCore::OpusDecoder::OpusDecoderManager impl;
39}; 34};
40 35
41} // namespace Service::Audio 36} // namespace Service::Audio
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 6e4d26b1e..c2054e8a0 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -329,6 +329,7 @@ public:
329 {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, 329 {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
330 {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, 330 {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"},
331 {15, nullptr, "QueryEntry"}, 331 {15, nullptr, "QueryEntry"},
332 {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"},
332 }; 333 };
333 RegisterHandlers(functions); 334 RegisterHandlers(functions);
334 } 335 }
@@ -521,6 +522,46 @@ public:
521 rb.PushRaw(vfs_timestamp); 522 rb.PushRaw(vfs_timestamp);
522 } 523 }
523 524
525 void GetFileSystemAttribute(HLERequestContext& ctx) {
526 LOG_WARNING(Service_FS, "(STUBBED) called");
527
528 struct FileSystemAttribute {
529 u8 dir_entry_name_length_max_defined;
530 u8 file_entry_name_length_max_defined;
531 u8 dir_path_name_length_max_defined;
532 u8 file_path_name_length_max_defined;
533 INSERT_PADDING_BYTES_NOINIT(0x5);
534 u8 utf16_dir_entry_name_length_max_defined;
535 u8 utf16_file_entry_name_length_max_defined;
536 u8 utf16_dir_path_name_length_max_defined;
537 u8 utf16_file_path_name_length_max_defined;
538 INSERT_PADDING_BYTES_NOINIT(0x18);
539 s32 dir_entry_name_length_max;
540 s32 file_entry_name_length_max;
541 s32 dir_path_name_length_max;
542 s32 file_path_name_length_max;
543 INSERT_PADDING_WORDS_NOINIT(0x5);
544 s32 utf16_dir_entry_name_length_max;
545 s32 utf16_file_entry_name_length_max;
546 s32 utf16_dir_path_name_length_max;
547 s32 utf16_file_path_name_length_max;
548 INSERT_PADDING_WORDS_NOINIT(0x18);
549 INSERT_PADDING_WORDS_NOINIT(0x1);
550 };
551 static_assert(sizeof(FileSystemAttribute) == 0xc0,
552 "FileSystemAttribute has incorrect size");
553
554 FileSystemAttribute savedata_attribute{};
555 savedata_attribute.dir_entry_name_length_max_defined = true;
556 savedata_attribute.file_entry_name_length_max_defined = true;
557 savedata_attribute.dir_entry_name_length_max = 0x40;
558 savedata_attribute.file_entry_name_length_max = 0x40;
559
560 IPC::ResponseBuilder rb{ctx, 50};
561 rb.Push(ResultSuccess);
562 rb.PushRaw(savedata_attribute);
563 }
564
524private: 565private:
525 VfsDirectoryServiceWrapper backend; 566 VfsDirectoryServiceWrapper backend;
526 SizeGetter size; 567 SizeGetter size;
@@ -698,7 +739,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
698 {19, nullptr, "FormatSdCardFileSystem"}, 739 {19, nullptr, "FormatSdCardFileSystem"},
699 {21, nullptr, "DeleteSaveDataFileSystem"}, 740 {21, nullptr, "DeleteSaveDataFileSystem"},
700 {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, 741 {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
701 {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"}, 742 {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
702 {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, 743 {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
703 {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, 744 {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
704 {26, nullptr, "FormatSdCardDryRun"}, 745 {26, nullptr, "FormatSdCardDryRun"},
@@ -712,7 +753,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
712 {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, 753 {35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
713 {36, nullptr, "OpenHostFileSystemWithOption"}, 754 {36, nullptr, "OpenHostFileSystemWithOption"},
714 {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, 755 {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
715 {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, 756 {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
716 {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, 757 {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
717 {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, 758 {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
718 {58, nullptr, "ReadSaveDataFileSystemExtraData"}, 759 {58, nullptr, "ReadSaveDataFileSystemExtraData"},
@@ -870,6 +911,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
870 rb.Push(ResultSuccess); 911 rb.Push(ResultSuccess);
871} 912}
872 913
914void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
915 IPC::RequestParser rp{ctx};
916
917 auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
918 [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
919
920 LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
921
922 FileSys::VirtualDir save_data_dir{};
923 fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
924
925 IPC::ResponseBuilder rb{ctx, 2};
926 rb.Push(ResultSuccess);
927}
928
873void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { 929void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
874 IPC::RequestParser rp{ctx}; 930 IPC::RequestParser rp{ctx};
875 931
@@ -916,6 +972,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
916 rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); 972 rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
917} 973}
918 974
975void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
976 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
977 OpenSaveDataFileSystem(ctx);
978}
979
919void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { 980void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
920 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); 981 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
921 OpenSaveDataFileSystem(ctx); 982 OpenSaveDataFileSystem(ctx);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 4f3c2f6de..280bc9867 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -39,7 +39,9 @@ private:
39 void OpenFileSystemWithPatch(HLERequestContext& ctx); 39 void OpenFileSystemWithPatch(HLERequestContext& ctx);
40 void OpenSdCardFileSystem(HLERequestContext& ctx); 40 void OpenSdCardFileSystem(HLERequestContext& ctx);
41 void CreateSaveDataFileSystem(HLERequestContext& ctx); 41 void CreateSaveDataFileSystem(HLERequestContext& ctx);
42 void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
42 void OpenSaveDataFileSystem(HLERequestContext& ctx); 43 void OpenSaveDataFileSystem(HLERequestContext& ctx);
44 void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
43 void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); 45 void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
44 void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); 46 void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
45 void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); 47 void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 3b349b4c4..bc822f19e 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
193 shared_memory->system_properties.use_minus.Assign(1); 193 shared_memory->system_properties.use_minus.Assign(1);
194 shared_memory->system_properties.is_charging_joy_dual.Assign( 194 shared_memory->system_properties.is_charging_joy_dual.Assign(
195 battery_level.dual.is_charging); 195 battery_level.dual.is_charging);
196 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; 196 shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); 197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
198 break; 198 break;
199 case Core::HID::NpadStyleIndex::Handheld: 199 case Core::HID::NpadStyleIndex::Handheld:
@@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
216 shared_memory->system_properties.is_charging_joy_right.Assign( 216 shared_memory->system_properties.is_charging_joy_right.Assign(
217 battery_level.right.is_charging); 217 battery_level.right.is_charging);
218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
219 shared_memory->applet_nfc_xcd.applet_footer.type = 219 shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
220 AppletFooterUiType::HandheldJoyConLeftJoyConRight;
221 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); 220 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
222 break; 221 break;
223 case Core::HID::NpadStyleIndex::JoyconDual: 222 case Core::HID::NpadStyleIndex::JoyconDual:
@@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
247 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 246 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
248 247
249 if (controller.is_dual_left_connected && controller.is_dual_right_connected) { 248 if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
250 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; 249 shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
251 shared_memory->fullkey_color.fullkey = body_colors.left; 250 shared_memory->fullkey_color.fullkey = body_colors.left;
252 shared_memory->battery_level_dual = battery_level.left.battery_level; 251 shared_memory->battery_level_dual = battery_level.left.battery_level;
253 shared_memory->system_properties.is_charging_joy_dual.Assign( 252 shared_memory->system_properties.is_charging_joy_dual.Assign(
254 battery_level.left.is_charging); 253 battery_level.left.is_charging);
255 } else if (controller.is_dual_left_connected) { 254 } else if (controller.is_dual_left_connected) {
256 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; 255 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
257 shared_memory->fullkey_color.fullkey = body_colors.left; 256 shared_memory->fullkey_color.fullkey = body_colors.left;
258 shared_memory->battery_level_dual = battery_level.left.battery_level; 257 shared_memory->battery_level_dual = battery_level.left.battery_level;
259 shared_memory->system_properties.is_charging_joy_dual.Assign( 258 shared_memory->system_properties.is_charging_joy_dual.Assign(
260 battery_level.left.is_charging); 259 battery_level.left.is_charging);
261 } else { 260 } else {
262 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; 261 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
263 shared_memory->fullkey_color.fullkey = body_colors.right; 262 shared_memory->fullkey_color.fullkey = body_colors.right;
264 shared_memory->battery_level_dual = battery_level.right.battery_level; 263 shared_memory->battery_level_dual = battery_level.right.battery_level;
265 shared_memory->system_properties.is_charging_joy_dual.Assign( 264 shared_memory->system_properties.is_charging_joy_dual.Assign(
@@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
278 shared_memory->system_properties.use_minus.Assign(1); 277 shared_memory->system_properties.use_minus.Assign(1);
279 shared_memory->system_properties.is_charging_joy_left.Assign( 278 shared_memory->system_properties.is_charging_joy_left.Assign(
280 battery_level.left.is_charging); 279 battery_level.left.is_charging);
281 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; 280 shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
282 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); 281 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
283 break; 282 break;
284 case Core::HID::NpadStyleIndex::JoyconRight: 283 case Core::HID::NpadStyleIndex::JoyconRight:
@@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
293 shared_memory->system_properties.use_plus.Assign(1); 292 shared_memory->system_properties.use_plus.Assign(1);
294 shared_memory->system_properties.is_charging_joy_right.Assign( 293 shared_memory->system_properties.is_charging_joy_right.Assign(
295 battery_level.right.is_charging); 294 battery_level.right.is_charging);
296 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; 295 shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
297 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); 296 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
298 break; 297 break;
299 case Core::HID::NpadStyleIndex::GameCube: 298 case Core::HID::NpadStyleIndex::GameCube:
@@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
314 case Core::HID::NpadStyleIndex::SNES: 313 case Core::HID::NpadStyleIndex::SNES:
315 shared_memory->style_tag.lucia.Assign(1); 314 shared_memory->style_tag.lucia.Assign(1);
316 shared_memory->device_type.fullkey.Assign(1); 315 shared_memory->device_type.fullkey.Assign(1);
317 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; 316 shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
318 break; 317 break;
319 case Core::HID::NpadStyleIndex::N64: 318 case Core::HID::NpadStyleIndex::N64:
320 shared_memory->style_tag.lagoon.Assign(1); 319 shared_memory->style_tag.lagoon.Assign(1);
321 shared_memory->device_type.fullkey.Assign(1); 320 shared_memory->device_type.fullkey.Assign(1);
322 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; 321 shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
323 break; 322 break;
324 case Core::HID::NpadStyleIndex::SegaGenesis: 323 case Core::HID::NpadStyleIndex::SegaGenesis:
325 shared_memory->style_tag.lager.Assign(1); 324 shared_memory->style_tag.lager.Assign(1);
@@ -347,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
347 } 346 }
348 SignalStyleSetChangedEvent(npad_id); 347 SignalStyleSetChangedEvent(npad_id);
349 WriteEmptyEntry(controller.shared_memory); 348 WriteEmptyEntry(controller.shared_memory);
349 hid_core.SetLastActiveController(npad_id);
350} 350}
351 351
352void Controller_NPad::OnInit() { 352void Controller_NPad::OnInit() {
@@ -419,9 +419,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
419 std::scoped_lock lock{mutex}; 419 std::scoped_lock lock{mutex};
420 auto& controller = GetControllerFromNpadIdType(npad_id); 420 auto& controller = GetControllerFromNpadIdType(npad_id);
421 const auto controller_type = controller.device->GetNpadStyleIndex(); 421 const auto controller_type = controller.device->GetNpadStyleIndex();
422
423 if (!controller.device->IsConnected() && controller.is_connected) {
424 DisconnectNpad(npad_id);
425 return;
426 }
422 if (!controller.device->IsConnected()) { 427 if (!controller.device->IsConnected()) {
423 return; 428 return;
424 } 429 }
430 if (controller.device->IsConnected() && !controller.is_connected) {
431 InitNewlyAddedController(npad_id);
432 }
425 433
426 // This function is unique to yuzu for the turbo buttons and motion to work properly 434 // This function is unique to yuzu for the turbo buttons and motion to work properly
427 controller.device->StatusUpdate(); 435 controller.device->StatusUpdate();
@@ -468,6 +476,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
468 pad_entry.npad_buttons.l.Assign(button_state.zl); 476 pad_entry.npad_buttons.l.Assign(button_state.zl);
469 pad_entry.npad_buttons.r.Assign(button_state.zr); 477 pad_entry.npad_buttons.r.Assign(button_state.zr);
470 } 478 }
479
480 if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
481 hid_core.SetLastActiveController(npad_id);
482 }
471} 483}
472 484
473void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { 485void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
@@ -736,14 +748,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
736 748
737 // Once SetSupportedStyleSet is called controllers are fully initialized 749 // Once SetSupportedStyleSet is called controllers are fully initialized
738 is_controller_initialized = true; 750 is_controller_initialized = true;
739
740 // Connect all active controllers
741 for (auto& controller : controller_data) {
742 const auto& device = controller.device;
743 if (device->IsConnected()) {
744 AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
745 }
746 }
747} 751}
748 752
749Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { 753Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
@@ -1116,7 +1120,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
1116 .left = {}, 1120 .left = {},
1117 .right = {}, 1121 .right = {},
1118 }; 1122 };
1119 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; 1123 shared_memory->applet_footer_type = AppletFooterUiType::None;
1120 1124
1121 controller.is_dual_left_connected = true; 1125 controller.is_dual_left_connected = true;
1122 controller.is_dual_right_connected = true; 1126 controller.is_dual_right_connected = true;
@@ -1508,6 +1512,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
1508 return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); 1512 return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
1509} 1513}
1510 1514
1515void Controller_NPad::ApplyNpadSystemCommonPolicy() {
1516 Core::HID::NpadStyleTag styletag{};
1517 styletag.fullkey.Assign(1);
1518 styletag.handheld.Assign(1);
1519 styletag.joycon_dual.Assign(1);
1520 styletag.system_ext.Assign(1);
1521 styletag.system.Assign(1);
1522 SetSupportedStyleSet(styletag);
1523
1524 SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
1525
1526 supported_npad_id_types.clear();
1527 supported_npad_id_types.resize(10);
1528 supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
1529 supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
1530 supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
1531 supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
1532 supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
1533 supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
1534 supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
1535 supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
1536 supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
1537 supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
1538}
1539
1511bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { 1540bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
1512 if (controller == Core::HID::NpadStyleIndex::Handheld) { 1541 if (controller == Core::HID::NpadStyleIndex::Handheld) {
1513 const bool support_handheld = 1542 const bool support_handheld =
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 776411261..949e58a4c 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -190,6 +190,8 @@ public:
190 // Specifically for cheat engine and other features. 190 // Specifically for cheat engine and other features.
191 Core::HID::NpadButton GetAndResetPressState(); 191 Core::HID::NpadButton GetAndResetPressState();
192 192
193 void ApplyNpadSystemCommonPolicy();
194
193 static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); 195 static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
194 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); 196 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
195 static Result VerifyValidSixAxisSensorHandle( 197 static Result VerifyValidSixAxisSensorHandle(
@@ -360,7 +362,7 @@ private:
360 enum class AppletFooterUiType : u8 { 362 enum class AppletFooterUiType : u8 {
361 None = 0, 363 None = 0,
362 HandheldNone = 1, 364 HandheldNone = 1,
363 HandheldJoyConLeftOnly = 1, 365 HandheldJoyConLeftOnly = 2,
364 HandheldJoyConRightOnly = 3, 366 HandheldJoyConRightOnly = 3,
365 HandheldJoyConLeftJoyConRight = 4, 367 HandheldJoyConLeftJoyConRight = 4,
366 JoyDual = 5, 368 JoyDual = 5,
@@ -382,13 +384,6 @@ private:
382 Lagon = 21, 384 Lagon = 21,
383 }; 385 };
384 386
385 struct AppletFooterUi {
386 AppletFooterUiAttributes attributes{};
387 AppletFooterUiType type{AppletFooterUiType::None};
388 INSERT_PADDING_BYTES(0x5B); // Reserved
389 };
390 static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
391
392 // This is nn::hid::NpadLarkType 387 // This is nn::hid::NpadLarkType
393 enum class NpadLarkType : u32 { 388 enum class NpadLarkType : u32 {
394 Invalid, 389 Invalid,
@@ -419,13 +414,6 @@ private:
419 U, 414 U,
420 }; 415 };
421 416
422 struct AppletNfcXcd {
423 union {
424 AppletFooterUi applet_footer{};
425 Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
426 };
427 };
428
429 // This is nn::hid::detail::NpadInternalState 417 // This is nn::hid::detail::NpadInternalState
430 struct NpadInternalState { 418 struct NpadInternalState {
431 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; 419 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
@@ -452,7 +440,9 @@ private:
452 Core::HID::NpadBatteryLevel battery_level_dual{}; 440 Core::HID::NpadBatteryLevel battery_level_dual{};
453 Core::HID::NpadBatteryLevel battery_level_left{}; 441 Core::HID::NpadBatteryLevel battery_level_left{};
454 Core::HID::NpadBatteryLevel battery_level_right{}; 442 Core::HID::NpadBatteryLevel battery_level_right{};
455 AppletNfcXcd applet_nfc_xcd{}; 443 AppletFooterUiAttributes applet_footer_attributes{};
444 AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
445 INSERT_PADDING_BYTES(0x5B); // Reserved
456 INSERT_PADDING_BYTES(0x20); // Unknown 446 INSERT_PADDING_BYTES(0x20); // Unknown
457 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; 447 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
458 NpadLarkType lark_type_l_and_main{}; 448 NpadLarkType lark_type_l_and_main{};
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fd466db7b..4d70006c1 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
231 return applet_resource; 231 return applet_resource;
232} 232}
233 233
234Hid::Hid(Core::System& system_) 234Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
235 : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { 235 : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{
236 system_,
237 service_name} {
236 // clang-format off 238 // clang-format off
237 static const FunctionInfo functions[] = { 239 static const FunctionInfo functions[] = {
238 {0, &Hid::CreateAppletResource, "CreateAppletResource"}, 240 {0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -2543,8 +2545,9 @@ public:
2543 2545
2544class HidSys final : public ServiceFramework<HidSys> { 2546class HidSys final : public ServiceFramework<HidSys> {
2545public: 2547public:
2546 explicit HidSys(Core::System& system_) 2548 explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
2547 : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} { 2549 : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"},
2550 applet_resource{applet_resource_} {
2548 // clang-format off 2551 // clang-format off
2549 static const FunctionInfo functions[] = { 2552 static const FunctionInfo functions[] = {
2550 {31, nullptr, "SendKeyboardLockKeyEvent"}, 2553 {31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -2756,9 +2759,12 @@ public:
2756 2759
2757private: 2760private:
2758 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { 2761 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
2759 // We already do this for homebrew so we can just stub it out
2760 LOG_WARNING(Service_HID, "called"); 2762 LOG_WARNING(Service_HID, "called");
2761 2763
2764 GetAppletResource()
2765 ->GetController<Controller_NPad>(HidController::NPad)
2766 .ApplyNpadSystemCommonPolicy();
2767
2762 IPC::ResponseBuilder rb{ctx, 2}; 2768 IPC::ResponseBuilder rb{ctx, 2};
2763 rb.Push(ResultSuccess); 2769 rb.Push(ResultSuccess);
2764 } 2770 }
@@ -2768,7 +2774,7 @@ private:
2768 2774
2769 IPC::ResponseBuilder rb{ctx, 3}; 2775 IPC::ResponseBuilder rb{ctx, 3};
2770 rb.Push(ResultSuccess); 2776 rb.Push(ResultSuccess);
2771 rb.PushEnum(Core::HID::NpadIdType::Handheld); 2777 rb.PushEnum(system.HIDCore().GetLastActiveController());
2772 } 2778 }
2773 2779
2774 void GetUniquePadsFromNpad(HLERequestContext& ctx) { 2780 void GetUniquePadsFromNpad(HLERequestContext& ctx) {
@@ -2821,17 +2827,28 @@ private:
2821 rb.PushRaw(touchscreen_config); 2827 rb.PushRaw(touchscreen_config);
2822 } 2828 }
2823 2829
2830 std::shared_ptr<IAppletResource> GetAppletResource() {
2831 if (applet_resource == nullptr) {
2832 applet_resource = std::make_shared<IAppletResource>(system, service_context);
2833 }
2834
2835 return applet_resource;
2836 }
2837
2824 Kernel::KEvent* joy_detach_event; 2838 Kernel::KEvent* joy_detach_event;
2825 KernelHelpers::ServiceContext service_context; 2839 KernelHelpers::ServiceContext service_context;
2840 std::shared_ptr<IAppletResource> applet_resource;
2826}; 2841};
2827 2842
2828void LoopProcess(Core::System& system) { 2843void LoopProcess(Core::System& system) {
2829 auto server_manager = std::make_unique<ServerManager>(system); 2844 auto server_manager = std::make_unique<ServerManager>(system);
2845 std::shared_ptr<IAppletResource> applet_resource;
2830 2846
2831 server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); 2847 server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource));
2832 server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); 2848 server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system));
2833 server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); 2849 server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system));
2834 server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); 2850 server_manager->RegisterNamedService("hid:sys",
2851 std::make_shared<HidSys>(system, applet_resource));
2835 2852
2836 server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); 2853 server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
2837 server_manager->RegisterNamedService("irs:sys", 2854 server_manager->RegisterNamedService("irs:sys",
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index f247b83c2..0ca43de93 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -95,7 +95,7 @@ private:
95 95
96class Hid final : public ServiceFramework<Hid> { 96class Hid final : public ServiceFramework<Hid> {
97public: 97public:
98 explicit Hid(Core::System& system_); 98 explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
99 ~Hid() override; 99 ~Hid() override;
100 100
101 std::shared_ptr<IAppletResource> GetAppletResource(); 101 std::shared_ptr<IAppletResource> GetAppletResource();
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 3b83c5ed7..c28eed926 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -8,6 +8,9 @@
8#include "core/hle/service/mii/mii.h" 8#include "core/hle/service/mii/mii.h"
9#include "core/hle/service/mii/mii_manager.h" 9#include "core/hle/service/mii/mii_manager.h"
10#include "core/hle/service/mii/mii_result.h" 10#include "core/hle/service/mii/mii_result.h"
11#include "core/hle/service/mii/types/char_info.h"
12#include "core/hle/service/mii/types/store_data.h"
13#include "core/hle/service/mii/types/ver3_store_data.h"
11#include "core/hle/service/server_manager.h" 14#include "core/hle/service/server_manager.h"
12#include "core/hle/service/service.h" 15#include "core/hle/service/service.h"
13 16
@@ -15,8 +18,10 @@ namespace Service::Mii {
15 18
16class IDatabaseService final : public ServiceFramework<IDatabaseService> { 19class IDatabaseService final : public ServiceFramework<IDatabaseService> {
17public: 20public:
18 explicit IDatabaseService(Core::System& system_, bool is_system_) 21 explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
19 : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { 22 bool is_system_)
23 : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
24 is_system_} {
20 // clang-format off 25 // clang-format off
21 static const FunctionInfo functions[] = { 26 static const FunctionInfo functions[] = {
22 {0, &IDatabaseService::IsUpdated, "IsUpdated"}, 27 {0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -27,29 +32,31 @@ public:
27 {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, 32 {5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
28 {6, &IDatabaseService::BuildRandom, "BuildRandom"}, 33 {6, &IDatabaseService::BuildRandom, "BuildRandom"},
29 {7, &IDatabaseService::BuildDefault, "BuildDefault"}, 34 {7, &IDatabaseService::BuildDefault, "BuildDefault"},
30 {8, nullptr, "Get2"}, 35 {8, &IDatabaseService::Get2, "Get2"},
31 {9, nullptr, "Get3"}, 36 {9, &IDatabaseService::Get3, "Get3"},
32 {10, nullptr, "UpdateLatest1"}, 37 {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
33 {11, nullptr, "FindIndex"}, 38 {11, &IDatabaseService::FindIndex, "FindIndex"},
34 {12, nullptr, "Move"}, 39 {12, &IDatabaseService::Move, "Move"},
35 {13, nullptr, "AddOrReplace"}, 40 {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
36 {14, nullptr, "Delete"}, 41 {14, &IDatabaseService::Delete, "Delete"},
37 {15, nullptr, "DestroyFile"}, 42 {15, &IDatabaseService::DestroyFile, "DestroyFile"},
38 {16, nullptr, "DeleteFile"}, 43 {16, &IDatabaseService::DeleteFile, "DeleteFile"},
39 {17, nullptr, "Format"}, 44 {17, &IDatabaseService::Format, "Format"},
40 {18, nullptr, "Import"}, 45 {18, nullptr, "Import"},
41 {19, nullptr, "Export"}, 46 {19, nullptr, "Export"},
42 {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, 47 {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
43 {21, &IDatabaseService::GetIndex, "GetIndex"}, 48 {21, &IDatabaseService::GetIndex, "GetIndex"},
44 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, 49 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
45 {23, &IDatabaseService::Convert, "Convert"}, 50 {23, &IDatabaseService::Convert, "Convert"},
46 {24, nullptr, "ConvertCoreDataToCharInfo"}, 51 {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
47 {25, nullptr, "ConvertCharInfoToCoreData"}, 52 {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
48 {26, nullptr, "Append"}, 53 {26, &IDatabaseService::Append, "Append"},
49 }; 54 };
50 // clang-format on 55 // clang-format on
51 56
52 RegisterHandlers(functions); 57 RegisterHandlers(functions);
58
59 manager->Initialize(metadata);
53 } 60 }
54 61
55private: 62private:
@@ -59,7 +66,7 @@ private:
59 66
60 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 67 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
61 68
62 const bool is_updated = manager.IsUpdated(metadata, source_flag); 69 const bool is_updated = manager->IsUpdated(metadata, source_flag);
63 70
64 IPC::ResponseBuilder rb{ctx, 3}; 71 IPC::ResponseBuilder rb{ctx, 3};
65 rb.Push(ResultSuccess); 72 rb.Push(ResultSuccess);
@@ -69,7 +76,7 @@ private:
69 void IsFullDatabase(HLERequestContext& ctx) { 76 void IsFullDatabase(HLERequestContext& ctx) {
70 LOG_DEBUG(Service_Mii, "called"); 77 LOG_DEBUG(Service_Mii, "called");
71 78
72 const bool is_full_database = manager.IsFullDatabase(); 79 const bool is_full_database = manager->IsFullDatabase();
73 80
74 IPC::ResponseBuilder rb{ctx, 3}; 81 IPC::ResponseBuilder rb{ctx, 3};
75 rb.Push(ResultSuccess); 82 rb.Push(ResultSuccess);
@@ -80,9 +87,9 @@ private:
80 IPC::RequestParser rp{ctx}; 87 IPC::RequestParser rp{ctx};
81 const auto source_flag{rp.PopRaw<SourceFlag>()}; 88 const auto source_flag{rp.PopRaw<SourceFlag>()};
82 89
83 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 90 const u32 mii_count = manager->GetCount(metadata, source_flag);
84 91
85 const u32 mii_count = manager.GetCount(metadata, source_flag); 92 LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
86 93
87 IPC::ResponseBuilder rb{ctx, 3}; 94 IPC::ResponseBuilder rb{ctx, 3};
88 rb.Push(ResultSuccess); 95 rb.Push(ResultSuccess);
@@ -94,16 +101,17 @@ private:
94 const auto source_flag{rp.PopRaw<SourceFlag>()}; 101 const auto source_flag{rp.PopRaw<SourceFlag>()};
95 const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; 102 const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
96 103
97 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
98
99 u32 mii_count{}; 104 u32 mii_count{};
100 std::vector<CharInfoElement> char_info_elements(output_size); 105 std::vector<CharInfoElement> char_info_elements(output_size);
101 Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag); 106 const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
102 107
103 if (mii_count != 0) { 108 if (mii_count != 0) {
104 ctx.WriteBuffer(char_info_elements); 109 ctx.WriteBuffer(char_info_elements);
105 } 110 }
106 111
112 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
113 output_size, mii_count);
114
107 IPC::ResponseBuilder rb{ctx, 3}; 115 IPC::ResponseBuilder rb{ctx, 3};
108 rb.Push(result); 116 rb.Push(result);
109 rb.Push(mii_count); 117 rb.Push(mii_count);
@@ -114,16 +122,17 @@ private:
114 const auto source_flag{rp.PopRaw<SourceFlag>()}; 122 const auto source_flag{rp.PopRaw<SourceFlag>()};
115 const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; 123 const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
116 124
117 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
118
119 u32 mii_count{}; 125 u32 mii_count{};
120 std::vector<CharInfo> char_info(output_size); 126 std::vector<CharInfo> char_info(output_size);
121 Result result = manager.Get(metadata, char_info, mii_count, source_flag); 127 const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
122 128
123 if (mii_count != 0) { 129 if (mii_count != 0) {
124 ctx.WriteBuffer(char_info); 130 ctx.WriteBuffer(char_info);
125 } 131 }
126 132
133 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
134 output_size, mii_count);
135
127 IPC::ResponseBuilder rb{ctx, 3}; 136 IPC::ResponseBuilder rb{ctx, 3};
128 rb.Push(result); 137 rb.Push(result);
129 rb.Push(mii_count); 138 rb.Push(mii_count);
@@ -134,10 +143,10 @@ private:
134 const auto char_info{rp.PopRaw<CharInfo>()}; 143 const auto char_info{rp.PopRaw<CharInfo>()};
135 const auto source_flag{rp.PopRaw<SourceFlag>()}; 144 const auto source_flag{rp.PopRaw<SourceFlag>()};
136 145
137 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 146 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
138 147
139 CharInfo new_char_info{}; 148 CharInfo new_char_info{};
140 const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); 149 const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
141 if (result.IsFailure()) { 150 if (result.IsFailure()) {
142 IPC::ResponseBuilder rb{ctx, 2}; 151 IPC::ResponseBuilder rb{ctx, 2};
143 rb.Push(result); 152 rb.Push(result);
@@ -146,7 +155,7 @@ private:
146 155
147 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 156 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
148 rb.Push(ResultSuccess); 157 rb.Push(ResultSuccess);
149 rb.PushRaw<CharInfo>(new_char_info); 158 rb.PushRaw(new_char_info);
150 } 159 }
151 160
152 void BuildRandom(HLERequestContext& ctx) { 161 void BuildRandom(HLERequestContext& ctx) {
@@ -176,18 +185,18 @@ private:
176 } 185 }
177 186
178 CharInfo char_info{}; 187 CharInfo char_info{};
179 manager.BuildRandom(char_info, age, gender, race); 188 manager->BuildRandom(char_info, age, gender, race);
180 189
181 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 190 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
182 rb.Push(ResultSuccess); 191 rb.Push(ResultSuccess);
183 rb.PushRaw<CharInfo>(char_info); 192 rb.PushRaw(char_info);
184 } 193 }
185 194
186 void BuildDefault(HLERequestContext& ctx) { 195 void BuildDefault(HLERequestContext& ctx) {
187 IPC::RequestParser rp{ctx}; 196 IPC::RequestParser rp{ctx};
188 const auto index{rp.Pop<u32>()}; 197 const auto index{rp.Pop<u32>()};
189 198
190 LOG_INFO(Service_Mii, "called with index={}", index); 199 LOG_DEBUG(Service_Mii, "called with index={}", index);
191 200
192 if (index > 5) { 201 if (index > 5) {
193 IPC::ResponseBuilder rb{ctx, 2}; 202 IPC::ResponseBuilder rb{ctx, 2};
@@ -196,11 +205,243 @@ private:
196 } 205 }
197 206
198 CharInfo char_info{}; 207 CharInfo char_info{};
199 manager.BuildDefault(char_info, index); 208 manager->BuildDefault(char_info, index);
200 209
201 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 210 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
202 rb.Push(ResultSuccess); 211 rb.Push(ResultSuccess);
203 rb.PushRaw<CharInfo>(char_info); 212 rb.PushRaw(char_info);
213 }
214
215 void Get2(HLERequestContext& ctx) {
216 IPC::RequestParser rp{ctx};
217 const auto source_flag{rp.PopRaw<SourceFlag>()};
218 const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
219
220 u32 mii_count{};
221 std::vector<StoreDataElement> store_data_elements(output_size);
222 const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
223
224 if (mii_count != 0) {
225 ctx.WriteBuffer(store_data_elements);
226 }
227
228 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
229 output_size, mii_count);
230
231 IPC::ResponseBuilder rb{ctx, 3};
232 rb.Push(result);
233 rb.Push(mii_count);
234 }
235
236 void Get3(HLERequestContext& ctx) {
237 IPC::RequestParser rp{ctx};
238 const auto source_flag{rp.PopRaw<SourceFlag>()};
239 const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
240
241 u32 mii_count{};
242 std::vector<StoreData> store_data(output_size);
243 const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
244
245 if (mii_count != 0) {
246 ctx.WriteBuffer(store_data);
247 }
248
249 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
250 output_size, mii_count);
251
252 IPC::ResponseBuilder rb{ctx, 3};
253 rb.Push(result);
254 rb.Push(mii_count);
255 }
256
257 void UpdateLatest1(HLERequestContext& ctx) {
258 IPC::RequestParser rp{ctx};
259 const auto store_data{rp.PopRaw<StoreData>()};
260 const auto source_flag{rp.PopRaw<SourceFlag>()};
261
262 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
263
264 Result result = ResultSuccess;
265 if (!is_system) {
266 result = ResultPermissionDenied;
267 }
268
269 StoreData new_store_data{};
270 if (result.IsSuccess()) {
271 result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
272 }
273
274 if (result.IsFailure()) {
275 IPC::ResponseBuilder rb{ctx, 2};
276 rb.Push(result);
277 return;
278 }
279
280 IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
281 rb.Push(ResultSuccess);
282 rb.PushRaw<StoreData>(new_store_data);
283 }
284
285 void FindIndex(HLERequestContext& ctx) {
286 IPC::RequestParser rp{ctx};
287 const auto create_id{rp.PopRaw<Common::UUID>()};
288 const auto is_special{rp.PopRaw<bool>()};
289
290 LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
291 create_id.FormattedString(), is_special);
292
293 const s32 index = manager->FindIndex(create_id, is_special);
294
295 IPC::ResponseBuilder rb{ctx, 3};
296 rb.Push(ResultSuccess);
297 rb.Push(index);
298 }
299
300 void Move(HLERequestContext& ctx) {
301 IPC::RequestParser rp{ctx};
302 const auto create_id{rp.PopRaw<Common::UUID>()};
303 const auto new_index{rp.PopRaw<s32>()};
304
305 LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
306 new_index);
307
308 Result result = ResultSuccess;
309 if (!is_system) {
310 result = ResultPermissionDenied;
311 }
312
313 if (result.IsSuccess()) {
314 const u32 count = manager->GetCount(metadata, SourceFlag::Database);
315 if (new_index < 0 || new_index >= static_cast<s32>(count)) {
316 result = ResultInvalidArgument;
317 }
318 }
319
320 if (result.IsSuccess()) {
321 result = manager->Move(metadata, new_index, create_id);
322 }
323
324 IPC::ResponseBuilder rb{ctx, 2};
325 rb.Push(result);
326 }
327
328 void AddOrReplace(HLERequestContext& ctx) {
329 IPC::RequestParser rp{ctx};
330 const auto store_data{rp.PopRaw<StoreData>()};
331
332 LOG_INFO(Service_Mii, "called");
333
334 Result result = ResultSuccess;
335
336 if (!is_system) {
337 result = ResultPermissionDenied;
338 }
339
340 if (result.IsSuccess()) {
341 result = manager->AddOrReplace(metadata, store_data);
342 }
343
344 IPC::ResponseBuilder rb{ctx, 2};
345 rb.Push(result);
346 }
347
348 void Delete(HLERequestContext& ctx) {
349 IPC::RequestParser rp{ctx};
350 const auto create_id{rp.PopRaw<Common::UUID>()};
351
352 LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
353
354 Result result = ResultSuccess;
355
356 if (!is_system) {
357 result = ResultPermissionDenied;
358 }
359
360 if (result.IsSuccess()) {
361 result = manager->Delete(metadata, create_id);
362 }
363
364 IPC::ResponseBuilder rb{ctx, 2};
365 rb.Push(result);
366 }
367
368 void DestroyFile(HLERequestContext& ctx) {
369 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
370 const bool is_db_test_mode_enabled = false;
371
372 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
373
374 Result result = ResultSuccess;
375
376 if (!is_db_test_mode_enabled) {
377 result = ResultTestModeOnly;
378 }
379
380 if (result.IsSuccess()) {
381 result = manager->DestroyFile(metadata);
382 }
383
384 IPC::ResponseBuilder rb{ctx, 2};
385 rb.Push(result);
386 }
387
388 void DeleteFile(HLERequestContext& ctx) {
389 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
390 const bool is_db_test_mode_enabled = false;
391
392 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
393
394 Result result = ResultSuccess;
395
396 if (!is_db_test_mode_enabled) {
397 result = ResultTestModeOnly;
398 }
399
400 if (result.IsSuccess()) {
401 result = manager->DeleteFile();
402 }
403
404 IPC::ResponseBuilder rb{ctx, 2};
405 rb.Push(result);
406 }
407
408 void Format(HLERequestContext& ctx) {
409 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
410 const bool is_db_test_mode_enabled = false;
411
412 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
413
414 Result result = ResultSuccess;
415
416 if (!is_db_test_mode_enabled) {
417 result = ResultTestModeOnly;
418 }
419
420 if (result.IsSuccess()) {
421 result = manager->Format(metadata);
422 }
423
424 IPC::ResponseBuilder rb{ctx, 2};
425 rb.Push(result);
426 }
427
428 void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
429 LOG_DEBUG(Service_Mii, "called");
430
431 bool is_broken_with_clear_flag = false;
432 Result result = ResultSuccess;
433
434 if (!is_system) {
435 result = ResultPermissionDenied;
436 }
437
438 if (result.IsSuccess()) {
439 is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
440 }
441
442 IPC::ResponseBuilder rb{ctx, 3};
443 rb.Push(result);
444 rb.Push<u8>(is_broken_with_clear_flag);
204 } 445 }
205 446
206 void GetIndex(HLERequestContext& ctx) { 447 void GetIndex(HLERequestContext& ctx) {
@@ -210,7 +451,7 @@ private:
210 LOG_DEBUG(Service_Mii, "called"); 451 LOG_DEBUG(Service_Mii, "called");
211 452
212 s32 index{}; 453 s32 index{};
213 const auto result = manager.GetIndex(metadata, info, index); 454 const auto result = manager->GetIndex(metadata, info, index);
214 455
215 IPC::ResponseBuilder rb{ctx, 3}; 456 IPC::ResponseBuilder rb{ctx, 3};
216 rb.Push(result); 457 rb.Push(result);
@@ -223,7 +464,7 @@ private:
223 464
224 LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); 465 LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
225 466
226 manager.SetInterfaceVersion(metadata, interface_version); 467 manager->SetInterfaceVersion(metadata, interface_version);
227 468
228 IPC::ResponseBuilder rb{ctx, 2}; 469 IPC::ResponseBuilder rb{ctx, 2};
229 rb.Push(ResultSuccess); 470 rb.Push(ResultSuccess);
@@ -236,51 +477,96 @@ private:
236 LOG_INFO(Service_Mii, "called"); 477 LOG_INFO(Service_Mii, "called");
237 478
238 CharInfo char_info{}; 479 CharInfo char_info{};
239 manager.ConvertV3ToCharInfo(char_info, mii_v3); 480 const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
240 481
241 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 482 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
242 rb.Push(ResultSuccess); 483 rb.Push(result);
243 rb.PushRaw<CharInfo>(char_info); 484 rb.PushRaw<CharInfo>(char_info);
244 } 485 }
245 486
246 MiiManager manager{}; 487 void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
247 DatabaseSessionMetadata metadata{}; 488 IPC::RequestParser rp{ctx};
248 bool is_system{}; 489 const auto core_data{rp.PopRaw<CoreData>()};
249};
250 490
251class MiiDBModule final : public ServiceFramework<MiiDBModule> { 491 LOG_INFO(Service_Mii, "called");
252public:
253 explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
254 : ServiceFramework{system_, name_}, is_system{is_system_} {
255 // clang-format off
256 static const FunctionInfo functions[] = {
257 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
258 };
259 // clang-format on
260 492
261 RegisterHandlers(functions); 493 CharInfo char_info{};
494 const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
495
496 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
497 rb.Push(result);
498 rb.PushRaw<CharInfo>(char_info);
262 } 499 }
263 500
264private: 501 void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
265 void GetDatabaseService(HLERequestContext& ctx) { 502 IPC::RequestParser rp{ctx};
266 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 503 const auto char_info{rp.PopRaw<CharInfo>()};
267 rb.Push(ResultSuccess);
268 rb.PushIpcInterface<IDatabaseService>(system, is_system);
269 504
270 LOG_DEBUG(Service_Mii, "called"); 505 LOG_INFO(Service_Mii, "called");
506
507 CoreData core_data{};
508 const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
509
510 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
511 rb.Push(result);
512 rb.PushRaw<CoreData>(core_data);
513 }
514
515 void Append(HLERequestContext& ctx) {
516 IPC::RequestParser rp{ctx};
517 const auto char_info{rp.PopRaw<CharInfo>()};
518
519 LOG_INFO(Service_Mii, "called");
520
521 const auto result = manager->Append(metadata, char_info);
522
523 IPC::ResponseBuilder rb{ctx, 2};
524 rb.Push(result);
271 } 525 }
272 526
527 std::shared_ptr<MiiManager> manager = nullptr;
528 DatabaseSessionMetadata metadata{};
273 bool is_system{}; 529 bool is_system{};
274}; 530};
275 531
532MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
533 std::shared_ptr<MiiManager> mii_manager, bool is_system_)
534 : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
535 // clang-format off
536 static const FunctionInfo functions[] = {
537 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
538 };
539 // clang-format on
540
541 RegisterHandlers(functions);
542
543 if (manager == nullptr) {
544 manager = std::make_shared<MiiManager>();
545 }
546}
547
548MiiDBModule::~MiiDBModule() = default;
549
550void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
551 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
552 rb.Push(ResultSuccess);
553 rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
554
555 LOG_DEBUG(Service_Mii, "called");
556}
557
558std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
559 return manager;
560}
561
276class MiiImg final : public ServiceFramework<MiiImg> { 562class MiiImg final : public ServiceFramework<MiiImg> {
277public: 563public:
278 explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { 564 explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
279 // clang-format off 565 // clang-format off
280 static const FunctionInfo functions[] = { 566 static const FunctionInfo functions[] = {
281 {0, nullptr, "Initialize"}, 567 {0, &MiiImg::Initialize, "Initialize"},
282 {10, nullptr, "Reload"}, 568 {10, nullptr, "Reload"},
283 {11, nullptr, "GetCount"}, 569 {11, &MiiImg::GetCount, "GetCount"},
284 {12, nullptr, "IsEmpty"}, 570 {12, nullptr, "IsEmpty"},
285 {13, nullptr, "IsFull"}, 571 {13, nullptr, "IsFull"},
286 {14, nullptr, "GetAttribute"}, 572 {14, nullptr, "GetAttribute"},
@@ -297,15 +583,32 @@ public:
297 583
298 RegisterHandlers(functions); 584 RegisterHandlers(functions);
299 } 585 }
586
587private:
588 void Initialize(HLERequestContext& ctx) {
589 LOG_INFO(Service_Mii, "called");
590
591 IPC::ResponseBuilder rb{ctx, 2};
592 rb.Push(ResultSuccess);
593 }
594
595 void GetCount(HLERequestContext& ctx) {
596 LOG_DEBUG(Service_Mii, "called");
597
598 IPC::ResponseBuilder rb{ctx, 3};
599 rb.Push(ResultSuccess);
600 rb.Push(0);
601 }
300}; 602};
301 603
302void LoopProcess(Core::System& system) { 604void LoopProcess(Core::System& system) {
303 auto server_manager = std::make_unique<ServerManager>(system); 605 auto server_manager = std::make_unique<ServerManager>(system);
606 std::shared_ptr<MiiManager> manager = nullptr;
304 607
305 server_manager->RegisterNamedService("mii:e", 608 server_manager->RegisterNamedService(
306 std::make_shared<MiiDBModule>(system, "mii:e", true)); 609 "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
307 server_manager->RegisterNamedService("mii:u", 610 server_manager->RegisterNamedService(
308 std::make_shared<MiiDBModule>(system, "mii:u", false)); 611 "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
309 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); 612 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
310 ServerManager::RunServer(std::move(server_manager)); 613 ServerManager::RunServer(std::move(server_manager));
311} 614}
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index ed4e3f62b..9aa4426f6 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.h
@@ -3,11 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/service.h"
7
6namespace Core { 8namespace Core {
7class System; 9class System;
8} 10}
9 11
10namespace Service::Mii { 12namespace Service::Mii {
13class MiiManager;
14
15class MiiDBModule final : public ServiceFramework<MiiDBModule> {
16public:
17 explicit MiiDBModule(Core::System& system_, const char* name_,
18 std::shared_ptr<MiiManager> mii_manager, bool is_system_);
19 ~MiiDBModule() override;
20
21 std::shared_ptr<MiiManager> GetMiiManager();
22
23private:
24 void GetDatabaseService(HLERequestContext& ctx);
25
26 std::shared_ptr<MiiManager> manager = nullptr;
27 bool is_system{};
28};
11 29
12void LoopProcess(Core::System& system); 30void LoopProcess(Core::System& system);
13 31
diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp
new file mode 100644
index 000000000..3803e58e2
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.cpp
@@ -0,0 +1,142 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/mii/mii_database.h"
5#include "core/hle/service/mii/mii_result.h"
6#include "core/hle/service/mii/mii_util.h"
7
8namespace Service::Mii {
9
10u8 NintendoFigurineDatabase::GetDatabaseLength() const {
11 return database_length;
12}
13
14bool NintendoFigurineDatabase::IsFull() const {
15 return database_length >= MaxDatabaseLength;
16}
17
18StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
19 StoreData store_data = miis.at(index);
20
21 // This hack is to make external database dumps compatible
22 store_data.SetDeviceChecksum();
23
24 return store_data;
25}
26
27u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
28 if (magic == MiiMagic) {
29 return GetDatabaseLength();
30 }
31
32 u32 mii_count{};
33 for (std::size_t index = 0; index < mii_count; ++index) {
34 const auto& store_data = Get(index);
35 if (!store_data.IsSpecial()) {
36 mii_count++;
37 }
38 }
39
40 return mii_count;
41}
42
43bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
44 const Common::UUID& create_id) const {
45 for (std::size_t index = 0; index < database_length; ++index) {
46 if (miis[index].GetCreateId() == create_id) {
47 out_index = static_cast<u32>(index);
48 return true;
49 }
50 }
51
52 return false;
53}
54
55Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
56 if (current_index == new_index) {
57 return ResultNotUpdated;
58 }
59
60 const StoreData store_data = miis[current_index];
61
62 if (new_index > current_index) {
63 // Shift left
64 const u32 index_diff = new_index - current_index;
65 for (std::size_t i = 0; i < index_diff; i++) {
66 miis[current_index + i] = miis[current_index + i + 1];
67 }
68 } else {
69 // Shift right
70 const u32 index_diff = current_index - new_index;
71 for (std::size_t i = 0; i < index_diff; i++) {
72 miis[current_index - i] = miis[current_index - i - 1];
73 }
74 }
75
76 miis[new_index] = store_data;
77 crc = GenerateDatabaseCrc();
78 return ResultSuccess;
79}
80
81void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
82 miis[index] = store_data;
83 crc = GenerateDatabaseCrc();
84}
85
86void NintendoFigurineDatabase::Add(const StoreData& store_data) {
87 miis[database_length] = store_data;
88 database_length++;
89 crc = GenerateDatabaseCrc();
90}
91
92void NintendoFigurineDatabase::Delete(u32 index) {
93 // Shift left
94 const s32 new_database_size = database_length - 1;
95 if (static_cast<s32>(index) < new_database_size) {
96 for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
97 miis[i] = miis[i + 1];
98 }
99 }
100
101 database_length = static_cast<u8>(new_database_size);
102 crc = GenerateDatabaseCrc();
103}
104
105void NintendoFigurineDatabase::CleanDatabase() {
106 miis = {};
107 version = 1;
108 magic = DatabaseMagic;
109 database_length = 0;
110 crc = GenerateDatabaseCrc();
111}
112
113void NintendoFigurineDatabase::CorruptCrc() {
114 crc = GenerateDatabaseCrc();
115 crc = ~crc;
116}
117
118Result NintendoFigurineDatabase::CheckIntegrity() {
119 if (magic != DatabaseMagic) {
120 return ResultInvalidDatabaseSignature;
121 }
122
123 if (version != 1) {
124 return ResultInvalidDatabaseVersion;
125 }
126
127 if (crc != GenerateDatabaseCrc()) {
128 return ResultInvalidDatabaseChecksum;
129 }
130
131 if (database_length >= MaxDatabaseLength) {
132 return ResultInvalidDatabaseLength;
133 }
134
135 return ResultSuccess;
136}
137
138u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
139 return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
140}
141
142} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h
new file mode 100644
index 000000000..3bd240f93
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.h
@@ -0,0 +1,66 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7#include "core/hle/service/mii/types/store_data.h"
8
9namespace Service::Mii {
10
11constexpr std::size_t MaxDatabaseLength{100};
12constexpr u32 MiiMagic{0xa523b78f};
13constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
14
15class NintendoFigurineDatabase {
16public:
17 /// Returns the total mii count.
18 u8 GetDatabaseLength() const;
19
20 /// Returns true if database is full.
21 bool IsFull() const;
22
23 /// Returns the mii of the specified index.
24 StoreData Get(std::size_t index) const;
25
26 /// Returns the total mii count. Ignoring special mii.
27 u32 GetCount(const DatabaseSessionMetadata& metadata) const;
28
29 /// Returns the index of a mii. If the mii isn't found returns false.
30 bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
31
32 /// Moves the location of a specific mii.
33 Result Move(u32 current_index, u32 new_index);
34
35 /// Replaces mii with new data.
36 void Replace(u32 index, const StoreData& store_data);
37
38 /// Adds a new mii to the end of the database.
39 void Add(const StoreData& store_data);
40
41 /// Removes mii from database and shifts left the remainding data.
42 void Delete(u32 index);
43
44 /// Deletes all contents with a fresh database
45 void CleanDatabase();
46
47 /// Intentionally sets a bad checksum
48 void CorruptCrc();
49
50 /// Returns success if database is valid otherwise returns the corresponding error code.
51 Result CheckIntegrity();
52
53private:
54 /// Returns the checksum of the database
55 u16 GenerateDatabaseCrc();
56
57 u32 magic{}; // 'NFDB'
58 std::array<StoreData, MaxDatabaseLength> miis{};
59 u8 version{};
60 u8 database_length{};
61 u16 crc{};
62};
63static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
64 "NintendoFigurineDatabase has incorrect size.");
65
66}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
new file mode 100644
index 000000000..0080b6705
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -0,0 +1,420 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/string_util.h"
10
11#include "core/hle/service/mii/mii_database_manager.h"
12#include "core/hle/service/mii/mii_result.h"
13#include "core/hle/service/mii/mii_util.h"
14#include "core/hle/service/mii/types/char_info.h"
15#include "core/hle/service/mii/types/store_data.h"
16
17namespace Service::Mii {
18const char* DbFileName = "MiiDatabase.dat";
19
20DatabaseManager::DatabaseManager() {}
21
22Result DatabaseManager::MountSaveData() {
23 if (!is_save_data_mounted) {
24 system_save_dir =
25 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
26 if (is_test_db) {
27 system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
28 "system/save/8000000000000031";
29 }
30
31 // mount point should be "mii:"
32
33 if (!Common::FS::CreateDirs(system_save_dir)) {
34 return ResultUnknown;
35 }
36 }
37
38 is_save_data_mounted = true;
39 return ResultSuccess;
40}
41
42Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
43 is_database_broken = false;
44 if (!is_save_data_mounted) {
45 return ResultInvalidArgument;
46 }
47
48 database.CleanDatabase();
49 update_counter++;
50 metadata.update_counter = update_counter;
51
52 const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
53 Common::FS::FileType::BinaryFile};
54
55 if (!db_file.IsOpen()) {
56 return SaveDatabase();
57 }
58
59 if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
60 is_database_broken = true;
61 }
62
63 if (db_file.Read(database) != 1) {
64 is_database_broken = true;
65 }
66
67 if (is_database_broken) {
68 // Dragons happen here for simplicity just clean the database
69 LOG_ERROR(Service_Mii, "Mii database is corrupted");
70 database.CleanDatabase();
71 return ResultUnknown;
72 }
73
74 const auto result = database.CheckIntegrity();
75
76 if (result.IsError()) {
77 LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
78 database.CleanDatabase();
79 return ResultSuccess;
80 }
81
82 LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
83 database.GetDatabaseLength());
84 return ResultSuccess;
85}
86
87bool DatabaseManager::IsFullDatabase() const {
88 return database.GetDatabaseLength() == MaxDatabaseLength;
89}
90
91bool DatabaseManager::IsModified() const {
92 return is_moddified;
93}
94
95u64 DatabaseManager::GetUpdateCounter() const {
96 return update_counter;
97}
98
99u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
100 const u32 database_size = database.GetDatabaseLength();
101 if (metadata.magic == MiiMagic) {
102 return database_size;
103 }
104
105 // Special mii can't be used. Skip those.
106
107 u32 mii_count{};
108 for (std::size_t index = 0; index < database_size; ++index) {
109 const auto& store_data = database.Get(index);
110 if (store_data.IsSpecial()) {
111 continue;
112 }
113 mii_count++;
114 }
115
116 return mii_count;
117}
118
119void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
120 const DatabaseSessionMetadata& metadata) const {
121 if (metadata.magic == MiiMagic) {
122 out_store_data = database.Get(index);
123 return;
124 }
125
126 // The index refeers to the mii index without special mii.
127 // Search on the database until we find it
128
129 u32 virtual_index = 0;
130 const u32 database_size = database.GetDatabaseLength();
131 for (std::size_t i = 0; i < database_size; ++i) {
132 const auto& store_data = database.Get(i);
133 if (store_data.IsSpecial()) {
134 continue;
135 }
136 if (virtual_index == index) {
137 out_store_data = store_data;
138 return;
139 }
140 virtual_index++;
141 }
142
143 // This function doesn't fail. It returns the first mii instead
144 out_store_data = database.Get(0);
145}
146
147Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
148 bool is_special) const {
149 u32 index{};
150 const bool is_found = database.GetIndexByCreatorId(index, create_id);
151
152 if (!is_found) {
153 return ResultNotFound;
154 }
155
156 if (is_special) {
157 out_index = index;
158 return ResultSuccess;
159 }
160
161 if (database.Get(index).IsSpecial()) {
162 return ResultNotFound;
163 }
164
165 out_index = 0;
166
167 if (index < 1) {
168 return ResultSuccess;
169 }
170
171 for (std::size_t i = 0; i < index; ++i) {
172 if (database.Get(i).IsSpecial()) {
173 continue;
174 }
175 out_index++;
176 }
177 return ResultSuccess;
178}
179
180Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
181 const Common::UUID& create_id) const {
182 u32 index{};
183 const bool is_found = database.GetIndexByCreatorId(index, create_id);
184
185 if (!is_found) {
186 return ResultNotFound;
187 }
188
189 if (metadata.magic == MiiMagic) {
190 out_index = index;
191 return ResultSuccess;
192 }
193
194 if (database.Get(index).IsSpecial()) {
195 return ResultNotFound;
196 }
197
198 out_index = 0;
199
200 if (index < 1) {
201 return ResultSuccess;
202 }
203
204 // The index refeers to the mii index without special mii.
205 // Search on the database until we find it
206
207 for (std::size_t i = 0; i <= index; ++i) {
208 const auto& store_data = database.Get(i);
209 if (store_data.IsSpecial()) {
210 continue;
211 }
212 out_index++;
213 }
214 return ResultSuccess;
215}
216
217Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
218 const Common::UUID& create_id) const {
219 const auto database_size = database.GetDatabaseLength();
220
221 if (database_size >= 1) {
222 u32 virtual_index{};
223 for (std::size_t i = 0; i < database_size; ++i) {
224 const StoreData& store_data = database.Get(i);
225 if (store_data.IsSpecial()) {
226 continue;
227 }
228 if (virtual_index == new_index) {
229 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
230 if (!is_found) {
231 return ResultNotFound;
232 }
233 if (store_data.IsSpecial()) {
234 return ResultInvalidOperation;
235 }
236 return ResultSuccess;
237 }
238 virtual_index++;
239 }
240 }
241
242 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
243 if (!is_found) {
244 return ResultNotFound;
245 }
246 const StoreData& store_data = database.Get(out_index);
247 if (store_data.IsSpecial()) {
248 return ResultInvalidOperation;
249 }
250 return ResultSuccess;
251}
252
253Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
254 const Common::UUID& create_id) {
255 u32 current_index{};
256 if (metadata.magic == MiiMagic) {
257 const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
258 if (!is_found) {
259 return ResultNotFound;
260 }
261 } else {
262 const auto result = FindMoveIndex(current_index, new_index, create_id);
263 if (result.IsError()) {
264 return result;
265 }
266 }
267
268 const auto result = database.Move(current_index, new_index);
269 if (result.IsFailure()) {
270 return result;
271 }
272
273 is_moddified = true;
274 update_counter++;
275 metadata.update_counter = update_counter;
276 return ResultSuccess;
277}
278
279Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
280 const StoreData& store_data) {
281 if (store_data.IsValid() != ValidationResult::NoErrors) {
282 return ResultInvalidStoreData;
283 }
284 if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
285 return ResultInvalidOperation;
286 }
287
288 u32 index{};
289 const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
290 if (is_found) {
291 const StoreData& old_store_data = database.Get(index);
292
293 if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
294 return ResultInvalidOperation;
295 }
296
297 database.Replace(index, store_data);
298 } else {
299 if (database.IsFull()) {
300 return ResultDatabaseFull;
301 }
302
303 database.Add(store_data);
304 }
305
306 is_moddified = true;
307 update_counter++;
308 metadata.update_counter = update_counter;
309 return ResultSuccess;
310}
311
312Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
313 u32 index{};
314 const bool is_found = database.GetIndexByCreatorId(index, create_id);
315 if (!is_found) {
316 return ResultNotFound;
317 }
318
319 if (metadata.magic != MiiMagic) {
320 const auto& store_data = database.Get(index);
321 if (store_data.IsSpecial()) {
322 return ResultInvalidOperation;
323 }
324 }
325
326 database.Delete(index);
327
328 is_moddified = true;
329 update_counter++;
330 metadata.update_counter = update_counter;
331 return ResultSuccess;
332}
333
334Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
335 if (char_info.Verify() != ValidationResult::NoErrors) {
336 return ResultInvalidCharInfo2;
337 }
338 if (char_info.GetType() == 1) {
339 return ResultInvalidCharInfoType;
340 }
341
342 u32 index{};
343 StoreData store_data{};
344
345 // Loop until the mii we created is not on the database
346 do {
347 store_data.BuildWithCharInfo(char_info);
348 } while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
349
350 const Result result = store_data.Restore();
351
352 if (result.IsSuccess() || result == ResultNotUpdated) {
353 return AddOrReplace(metadata, store_data);
354 }
355
356 return result;
357}
358
359Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
360 database.CorruptCrc();
361
362 is_moddified = true;
363 update_counter++;
364 metadata.update_counter = update_counter;
365
366 const auto result = SaveDatabase();
367 database.CleanDatabase();
368
369 return result;
370}
371
372Result DatabaseManager::DeleteFile() {
373 const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
374 // TODO: Return proper FS error here
375 return result ? ResultSuccess : ResultUnknown;
376}
377
378void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
379 database.CleanDatabase();
380 is_moddified = true;
381 update_counter++;
382 metadata.update_counter = update_counter;
383}
384
385Result DatabaseManager::SaveDatabase() {
386 // TODO: Replace unknown error codes with proper FS error codes when available
387
388 if (!Common::FS::Exists(system_save_dir / DbFileName)) {
389 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
390 LOG_ERROR(Service_Mii, "Failed to create mii database");
391 return ResultUnknown;
392 }
393 }
394
395 const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
396 if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
397 if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
398 LOG_ERROR(Service_Mii, "Failed to delete mii database");
399 return ResultUnknown;
400 }
401 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
402 LOG_ERROR(Service_Mii, "Failed to create mii database");
403 return ResultUnknown;
404 }
405 }
406
407 const Common::FS::IOFile db_file{system_save_dir / DbFileName,
408 Common::FS::FileAccessMode::ReadWrite,
409 Common::FS::FileType::BinaryFile};
410
411 if (db_file.Write(database) != 1) {
412 LOG_ERROR(Service_Mii, "Failed to save mii database");
413 return ResultUnknown;
414 }
415
416 is_moddified = false;
417 return ResultSuccess;
418}
419
420} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h
new file mode 100644
index 000000000..52c32be82
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.h
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/fs/fs.h"
7#include "core/hle/result.h"
8#include "core/hle/service/mii/mii_database.h"
9
10namespace Service::Mii {
11class CharInfo;
12class StoreData;
13
14class DatabaseManager {
15public:
16 DatabaseManager();
17 Result MountSaveData();
18 Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
19
20 bool IsFullDatabase() const;
21 bool IsModified() const;
22 u64 GetUpdateCounter() const;
23
24 void Get(StoreData& out_store_data, std::size_t index,
25 const DatabaseSessionMetadata& metadata) const;
26 u32 GetCount(const DatabaseSessionMetadata& metadata) const;
27
28 Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
29 Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
30 const Common::UUID& create_id) const;
31 Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
32
33 Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
34 const Common::UUID& create_id);
35 Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
36 Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
37 Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
38
39 Result DestroyFile(DatabaseSessionMetadata& metadata);
40 Result DeleteFile();
41 void Format(DatabaseSessionMetadata& metadata);
42
43 Result SaveDatabase();
44
45private:
46 // This is the global value of
47 // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
48 bool is_test_db{};
49
50 bool is_moddified{};
51 bool is_save_data_mounted{};
52 u64 update_counter{};
53 NintendoFigurineDatabase database{};
54
55 std::filesystem::path system_save_dir{};
56};
57
58}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 292d63777..dcfd6b2e2 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,38 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 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 <cstring>
5#include <random>
6
7#include "common/assert.h"
8#include "common/logging/log.h" 4#include "common/logging/log.h"
9#include "common/string_util.h" 5#include "core/hle/service/mii/mii_database_manager.h"
10
11#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/mii/mii_manager.h" 6#include "core/hle/service/mii/mii_manager.h"
13#include "core/hle/service/mii/mii_result.h" 7#include "core/hle/service/mii/mii_result.h"
14#include "core/hle/service/mii/mii_util.h" 8#include "core/hle/service/mii/mii_util.h"
9#include "core/hle/service/mii/types/char_info.h"
15#include "core/hle/service/mii/types/core_data.h" 10#include "core/hle/service/mii/types/core_data.h"
16#include "core/hle/service/mii/types/raw_data.h" 11#include "core/hle/service/mii/types/raw_data.h"
12#include "core/hle/service/mii/types/store_data.h"
13#include "core/hle/service/mii/types/ver3_store_data.h"
17 14
18namespace Service::Mii { 15namespace Service::Mii {
19constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; 16constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
20 17
21MiiManager::MiiManager() {} 18MiiManager::MiiManager() {}
22 19
20Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
21 database_manager.MountSaveData();
22 database_manager.Initialize(metadata, is_broken_with_clear_flag);
23 return ResultSuccess;
24}
25
26void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
27 StoreData store_data{};
28 store_data.BuildDefault(index);
29 out_char_info.SetFromStoreData(store_data);
30}
31
32void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
33 StoreData store_data{};
34 store_data.BuildBase(gender);
35 out_char_info.SetFromStoreData(store_data);
36}
37
38void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
39 StoreData store_data{};
40 store_data.BuildRandom(age, gender, race);
41 out_char_info.SetFromStoreData(store_data);
42}
43
44bool MiiManager::IsFullDatabase() const {
45 return database_manager.IsFullDatabase();
46}
47
48void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
49 metadata.interface_version = version;
50}
51
23bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { 52bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
24 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 53 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
25 return false; 54 return false;
26 } 55 }
27 56
28 const auto metadata_update_counter = metadata.update_counter; 57 const u64 metadata_update_counter = metadata.update_counter;
29 metadata.update_counter = update_counter; 58 const u64 database_update_counter = database_manager.GetUpdateCounter();
30 return metadata_update_counter != update_counter; 59 metadata.update_counter = database_update_counter;
31} 60 return metadata_update_counter != database_update_counter;
32
33bool MiiManager::IsFullDatabase() const {
34 // TODO(bunnei): We don't implement the Mii database, so it cannot be full
35 return false;
36} 61}
37 62
38u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { 63u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
@@ -41,72 +66,343 @@ u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag sou
41 mii_count += DefaultMiiCount; 66 mii_count += DefaultMiiCount;
42 } 67 }
43 if ((source_flag & SourceFlag::Database) != SourceFlag::None) { 68 if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
44 // TODO(bunnei): We don't implement the Mii database, but when we do, update this 69 mii_count += database_manager.GetCount(metadata);
45 } 70 }
46 return mii_count; 71 return mii_count;
47} 72}
48 73
49Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, 74Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
50 const CharInfo& char_info, SourceFlag source_flag) { 75 const Common::UUID& create_id) {
51 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 76 const auto result = database_manager.Move(metadata, index, create_id);
77
78 if (result.IsFailure()) {
79 return result;
80 }
81
82 if (!database_manager.IsModified()) {
83 return ResultNotUpdated;
84 }
85
86 return database_manager.SaveDatabase();
87}
88
89Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
90 const auto result = database_manager.AddOrReplace(metadata, store_data);
91
92 if (result.IsFailure()) {
93 return result;
94 }
95
96 if (!database_manager.IsModified()) {
97 return ResultNotUpdated;
98 }
99
100 return database_manager.SaveDatabase();
101}
102
103Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
104 const auto result = database_manager.Delete(metadata, create_id);
105
106 if (result.IsFailure()) {
107 return result;
108 }
109
110 if (!database_manager.IsModified()) {
111 return ResultNotUpdated;
112 }
113
114 return database_manager.SaveDatabase();
115}
116
117s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
118 s32 index{};
119 const auto result = database_manager.FindIndex(index, create_id, is_special);
120 if (result.IsError()) {
121 index = -1;
122 }
123 return index;
124}
125
126Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
127 s32& out_index) const {
128 if (char_info.Verify() != ValidationResult::NoErrors) {
129 return ResultInvalidCharInfo;
130 }
131
132 s32 index{};
133 const bool is_special = metadata.magic == MiiMagic;
134 const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
135
136 if (result.IsError()) {
137 index = -1;
138 }
139
140 if (index == -1) {
52 return ResultNotFound; 141 return ResultNotFound;
53 } 142 }
54 143
55 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 144 out_index = index;
56 return ResultNotFound; 145 return ResultSuccess;
57} 146}
58 147
59void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { 148Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
60 StoreData store_data{}; 149 const auto result = database_manager.Append(metadata, char_info);
61 store_data.BuildDefault(index); 150
62 out_char_info.SetFromStoreData(store_data); 151 if (result.IsError()) {
152 return ResultNotFound;
153 }
154
155 if (!database_manager.IsModified()) {
156 return ResultNotUpdated;
157 }
158
159 return database_manager.SaveDatabase();
63} 160}
64 161
65void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { 162bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
163 const bool is_broken = is_broken_with_clear_flag;
164 if (is_broken_with_clear_flag) {
165 is_broken_with_clear_flag = false;
166 database_manager.Format(metadata);
167 database_manager.SaveDatabase();
168 }
169 return is_broken;
170}
171
172Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
173 is_broken_with_clear_flag = true;
174 return database_manager.DestroyFile(metadata);
175}
176
177Result MiiManager::DeleteFile() {
178 return database_manager.DeleteFile();
179}
180
181Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
182 database_manager.Format(metadata);
183
184 if (!database_manager.IsModified()) {
185 return ResultNotUpdated;
186 }
187 return database_manager.SaveDatabase();
188}
189
190Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
191 if (!mii_v3.IsValid()) {
192 return ResultInvalidCharInfo;
193 }
194
66 StoreData store_data{}; 195 StoreData store_data{};
67 store_data.BuildBase(gender); 196 mii_v3.BuildToStoreData(store_data);
197 const auto name = store_data.GetNickname();
198 if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
199 store_data.SetInvalidName();
200 }
201
68 out_char_info.SetFromStoreData(store_data); 202 out_char_info.SetFromStoreData(store_data);
203 return ResultSuccess;
69} 204}
70 205
71void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { 206Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
207 const CoreData& core_data) const {
208 if (core_data.IsValid() != ValidationResult::NoErrors) {
209 return ResultInvalidCharInfo;
210 }
211
72 StoreData store_data{}; 212 StoreData store_data{};
73 store_data.BuildRandom(age, gender, race); 213 store_data.BuildWithCoreData(core_data);
214 const auto name = store_data.GetNickname();
215 if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
216 store_data.SetInvalidName();
217 }
218
74 out_char_info.SetFromStoreData(store_data); 219 out_char_info.SetFromStoreData(store_data);
220 return ResultSuccess;
221}
222
223Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
224 const CharInfo& char_info) const {
225 if (char_info.Verify() != ValidationResult::NoErrors) {
226 return ResultInvalidCharInfo;
227 }
228
229 out_core_data.BuildFromCharInfo(char_info);
230 const auto name = out_core_data.GetNickname();
231 if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
232 out_core_data.SetNickname(out_core_data.GetInvalidNickname());
233 }
234
235 return ResultSuccess;
75} 236}
76 237
77void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { 238Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
239 const CharInfo& char_info, SourceFlag source_flag) const {
240 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
241 return ResultNotFound;
242 }
243
244 if (metadata.IsInterfaceVersionSupported(1)) {
245 if (char_info.Verify() != ValidationResult::NoErrors) {
246 return ResultInvalidCharInfo;
247 }
248 }
249
250 u32 index{};
251 Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
252
253 if (result.IsError()) {
254 return result;
255 }
256
78 StoreData store_data{}; 257 StoreData store_data{};
79 mii_v3.BuildToStoreData(store_data); 258 database_manager.Get(store_data, index, metadata);
259
260 if (store_data.GetType() != char_info.GetType()) {
261 return ResultNotFound;
262 }
263
80 out_char_info.SetFromStoreData(store_data); 264 out_char_info.SetFromStoreData(store_data);
265
266 if (char_info == out_char_info) {
267 return ResultNotUpdated;
268 }
269
270 return ResultSuccess;
271}
272
273Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
274 const StoreData& store_data, SourceFlag source_flag) const {
275 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
276 return ResultNotFound;
277 }
278
279 if (metadata.IsInterfaceVersionSupported(1)) {
280 if (store_data.IsValid() != ValidationResult::NoErrors) {
281 return ResultInvalidCharInfo;
282 }
283 }
284
285 u32 index{};
286 Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
287
288 if (result.IsError()) {
289 return result;
290 }
291
292 database_manager.Get(out_store_data, index, metadata);
293
294 if (out_store_data.GetType() != store_data.GetType()) {
295 return ResultNotFound;
296 }
297
298 if (store_data == out_store_data) {
299 return ResultNotUpdated;
300 }
301
302 return ResultSuccess;
81} 303}
82 304
83Result MiiManager::Get(const DatabaseSessionMetadata& metadata, 305Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
84 std::span<CharInfoElement> out_elements, u32& out_count, 306 std::span<CharInfoElement> out_elements, u32& out_count,
85 SourceFlag source_flag) { 307 SourceFlag source_flag) const {
86 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 308 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
87 return BuildDefault(out_elements, out_count, source_flag); 309 return BuildDefault(out_elements, out_count, source_flag);
88 } 310 }
89 311
90 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 312 const auto mii_count = database_manager.GetCount(metadata);
313
314 for (std::size_t index = 0; index < mii_count; ++index) {
315 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
316 return ResultInvalidArgumentSize;
317 }
318
319 StoreData store_data{};
320 database_manager.Get(store_data, index, metadata);
321
322 out_elements[out_count].source = Source::Database;
323 out_elements[out_count].char_info.SetFromStoreData(store_data);
324 out_count++;
325 }
91 326
92 // Include default Mii at the end of the list 327 // Include default Mii at the end of the list
93 return BuildDefault(out_elements, out_count, source_flag); 328 return BuildDefault(out_elements, out_count, source_flag);
94} 329}
95 330
96Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, 331Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
97 u32& out_count, SourceFlag source_flag) { 332 u32& out_count, SourceFlag source_flag) const {
98 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 333 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
99 return BuildDefault(out_char_info, out_count, source_flag); 334 return BuildDefault(out_char_info, out_count, source_flag);
100 } 335 }
101 336
102 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 337 const auto mii_count = database_manager.GetCount(metadata);
338
339 for (std::size_t index = 0; index < mii_count; ++index) {
340 if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
341 return ResultInvalidArgumentSize;
342 }
343
344 StoreData store_data{};
345 database_manager.Get(store_data, index, metadata);
346
347 out_char_info[out_count].SetFromStoreData(store_data);
348 out_count++;
349 }
103 350
104 // Include default Mii at the end of the list 351 // Include default Mii at the end of the list
105 return BuildDefault(out_char_info, out_count, source_flag); 352 return BuildDefault(out_char_info, out_count, source_flag);
106} 353}
107 354
355Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
356 std::span<StoreDataElement> out_elements, u32& out_count,
357 SourceFlag source_flag) const {
358 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
359 return BuildDefault(out_elements, out_count, source_flag);
360 }
361
362 const auto mii_count = database_manager.GetCount(metadata);
363
364 for (std::size_t index = 0; index < mii_count; ++index) {
365 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
366 return ResultInvalidArgumentSize;
367 }
368
369 StoreData store_data{};
370 database_manager.Get(store_data, index, metadata);
371
372 out_elements[out_count].store_data = store_data;
373 out_elements[out_count].source = Source::Database;
374 out_count++;
375 }
376
377 // Include default Mii at the end of the list
378 return BuildDefault(out_elements, out_count, source_flag);
379}
380
381Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
382 u32& out_count, SourceFlag source_flag) const {
383 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
384 return BuildDefault(out_store_data, out_count, source_flag);
385 }
386
387 const auto mii_count = database_manager.GetCount(metadata);
388
389 for (std::size_t index = 0; index < mii_count; ++index) {
390 if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
391 return ResultInvalidArgumentSize;
392 }
393
394 StoreData store_data{};
395 database_manager.Get(store_data, index, metadata);
396
397 out_store_data[out_count] = store_data;
398 out_count++;
399 }
400
401 // Include default Mii at the end of the list
402 return BuildDefault(out_store_data, out_count, source_flag);
403}
108Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, 404Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
109 SourceFlag source_flag) { 405 SourceFlag source_flag) const {
110 if ((source_flag & SourceFlag::Default) == SourceFlag::None) { 406 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
111 return ResultSuccess; 407 return ResultSuccess;
112 } 408 }
@@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou
129} 425}
130 426
131Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, 427Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
132 SourceFlag source_flag) { 428 SourceFlag source_flag) const {
133 if ((source_flag & SourceFlag::Default) == SourceFlag::None) { 429 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
134 return ResultSuccess; 430 return ResultSuccess;
135 } 431 }
@@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun
150 return ResultSuccess; 446 return ResultSuccess;
151} 447}
152 448
153Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, 449Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
154 s32& out_index) { 450 SourceFlag source_flag) const {
155 451 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
156 if (char_info.Verify() != ValidationResult::NoErrors) { 452 return ResultSuccess;
157 return ResultInvalidCharInfo;
158 } 453 }
159 454
160 constexpr u32 INVALID_INDEX{0xFFFFFFFF}; 455 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
456 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
457 return ResultInvalidArgumentSize;
458 }
161 459
162 out_index = INVALID_INDEX; 460 out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
461 out_elements[out_count].source = Source::Default;
462 out_count++;
463 }
163 464
164 // TODO(bunnei): We don't implement the Mii database, so we can't have an index 465 return ResultSuccess;
165 return ResultNotFound;
166} 466}
167 467
168void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { 468Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
169 metadata.interface_version = version; 469 SourceFlag source_flag) const {
470 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
471 return ResultSuccess;
472 }
473
474 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
475 if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
476 return ResultInvalidArgumentSize;
477 }
478
479 out_char_info[out_count].BuildDefault(static_cast<u32>(index));
480 out_count++;
481 }
482
483 return ResultSuccess;
170} 484}
171 485
172} // namespace Service::Mii 486} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index a2e7a6d73..48d8e8bb7 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -3,47 +3,85 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <vector> 6#include <span>
7 7
8#include "core/hle/result.h" 8#include "core/hle/result.h"
9#include "core/hle/service/mii/mii_database_manager.h"
9#include "core/hle/service/mii/mii_types.h" 10#include "core/hle/service/mii/mii_types.h"
10#include "core/hle/service/mii/types/char_info.h"
11#include "core/hle/service/mii/types/store_data.h"
12#include "core/hle/service/mii/types/ver3_store_data.h"
13 11
14namespace Service::Mii { 12namespace Service::Mii {
13class CharInfo;
14class CoreData;
15class StoreData;
16class Ver3StoreData;
15 17
16// The Mii manager is responsible for loading and storing the Miis to the database in NAND along 18struct CharInfoElement;
17// with providing an easy interface for HLE emulation of the mii service. 19struct StoreDataElement;
20
21// The Mii manager is responsible for handling mii operations along with providing an easy interface
22// for HLE emulation of the mii service.
18class MiiManager { 23class MiiManager {
19public: 24public:
20 MiiManager(); 25 MiiManager();
26 Result Initialize(DatabaseSessionMetadata& metadata);
21 27
22 bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; 28 // Auto generated mii
29 void BuildDefault(CharInfo& out_char_info, u32 index) const;
30 void BuildBase(CharInfo& out_char_info, Gender gender) const;
31 void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
23 32
33 // Database operations
24 bool IsFullDatabase() const; 34 bool IsFullDatabase() const;
35 void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
36 bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
25 u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; 37 u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
26 Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, 38 Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
27 const CharInfo& char_info, SourceFlag source_flag); 39 Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
40 Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
41 s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
42 Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
43 s32& out_index) const;
44 Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
45
46 // Test database operations
47 bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
48 Result DestroyFile(DatabaseSessionMetadata& metadata);
49 Result DeleteFile();
50 Result Format(DatabaseSessionMetadata& metadata);
51
52 // Mii conversions
53 Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
54 Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
55 Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
56 Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
57 const CharInfo& char_info, SourceFlag source_flag) const;
58 Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
59 const StoreData& store_data, SourceFlag source_flag) const;
60
61 // Overloaded getters
28 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, 62 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
29 u32& out_count, SourceFlag source_flag); 63 u32& out_count, SourceFlag source_flag) const;
30 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, 64 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
31 u32& out_count, SourceFlag source_flag); 65 u32& out_count, SourceFlag source_flag) const;
32 void BuildDefault(CharInfo& out_char_info, u32 index) const; 66 Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
33 void BuildBase(CharInfo& out_char_info, Gender gender) const; 67 u32& out_count, SourceFlag source_flag) const;
34 void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; 68 Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
35 void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; 69 u32& out_count, SourceFlag source_flag) const;
36 std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
37 Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
38 s32& out_index);
39 void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
40 70
41private: 71private:
42 Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, 72 Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
43 SourceFlag source_flag); 73 SourceFlag source_flag) const;
44 Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); 74 Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
75 SourceFlag source_flag) const;
76 Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
77 SourceFlag source_flag) const;
78 Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
79 SourceFlag source_flag) const;
80
81 DatabaseManager database_manager{};
45 82
46 u64 update_counter{}; 83 // This should be a global value
84 bool is_broken_with_clear_flag{};
47}; 85};
48 86
49}; // namespace Service::Mii 87}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h
index 021cb76da..e2c36e556 100644
--- a/src/core/hle/service/mii/mii_result.h
+++ b/src/core/hle/service/mii/mii_result.h
@@ -1,4 +1,4 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 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#pragma once 4#pragma once
@@ -13,8 +13,15 @@ constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
13constexpr Result ResultNotFound{ErrorModule::Mii, 4}; 13constexpr Result ResultNotFound{ErrorModule::Mii, 4};
14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; 14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; 15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
16constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
17constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
18constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
19constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
20constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
16constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; 21constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
17constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; 22constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
18constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; 23constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
24constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
25constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
19 26
20}; // namespace Service::Mii 27}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h
index 95476f745..08c6029df 100644
--- a/src/core/hle/service/mii/mii_types.h
+++ b/src/core/hle/service/mii/mii_types.h
@@ -1,4 +1,4 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 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#pragma once 4#pragma once
@@ -13,6 +13,7 @@
13 13
14namespace Service::Mii { 14namespace Service::Mii {
15 15
16constexpr std::size_t MaxNameSize = 10;
16constexpr u8 MaxHeight = 127; 17constexpr u8 MaxHeight = 127;
17constexpr u8 MaxBuild = 127; 18constexpr u8 MaxBuild = 127;
18constexpr u8 MaxType = 1; 19constexpr u8 MaxType = 1;
@@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8;
26constexpr u8 MaxEyebrowAspect = 6; 27constexpr u8 MaxEyebrowAspect = 6;
27constexpr u8 MaxEyebrowRotate = 11; 28constexpr u8 MaxEyebrowRotate = 11;
28constexpr u8 MaxEyebrowX = 12; 29constexpr u8 MaxEyebrowX = 12;
29constexpr u8 MaxEyebrowY = 18; 30constexpr u8 MaxEyebrowY = 15;
30constexpr u8 MaxNoseScale = 8; 31constexpr u8 MaxNoseScale = 8;
31constexpr u8 MaxNoseY = 18; 32constexpr u8 MaxNoseY = 18;
32constexpr u8 MaxMouthScale = 8; 33constexpr u8 MaxMouthScale = 8;
33constexpr u8 MaxMoutAspect = 6; 34constexpr u8 MaxMoutAspect = 6;
34constexpr u8 MaxMouthY = 18; 35constexpr u8 MaxMouthY = 18;
35constexpr u8 MaxMustacheScale = 8; 36constexpr u8 MaxMustacheScale = 8;
36constexpr u8 MasMustacheY = 16; 37constexpr u8 MaxMustacheY = 16;
37constexpr u8 MaxGlassScale = 7; 38constexpr u8 MaxGlassScale = 7;
38constexpr u8 MaxGlassY = 20; 39constexpr u8 MaxGlassY = 20;
39constexpr u8 MaxMoleScale = 8; 40constexpr u8 MaxMoleScale = 8;
@@ -599,24 +600,21 @@ enum class ValidationResult : u32 {
599 InvalidRegionMove = 0x31, 600 InvalidRegionMove = 0x31,
600 InvalidCreateId = 0x32, 601 InvalidCreateId = 0x32,
601 InvalidName = 0x33, 602 InvalidName = 0x33,
603 InvalidChecksum = 0x34,
602 InvalidType = 0x35, 604 InvalidType = 0x35,
603}; 605};
604 606
605struct Nickname { 607struct Nickname {
606 static constexpr std::size_t MaxNameSize = 10; 608 std::array<char16_t, MaxNameSize> data{};
607 std::array<char16_t, MaxNameSize> data;
608 609
609 // Checks for null, non-zero terminated or dirty strings 610 // Checks for null or dirty strings
610 bool IsValid() const { 611 bool IsValid() const {
611 if (data[0] == 0) { 612 if (data[0] == 0) {
612 return false; 613 return false;
613 } 614 }
614 615
615 if (data[MaxNameSize] != 0) {
616 return false;
617 }
618 std::size_t index = 1; 616 std::size_t index = 1;
619 while (data[index] != 0) { 617 while (index < MaxNameSize && data[index] != 0) {
620 index++; 618 index++;
621 } 619 }
622 while (index < MaxNameSize && data[index] == 0) { 620 while (index < MaxNameSize && data[index] == 0) {
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h
index ddb544c23..3534fa31d 100644
--- a/src/core/hle/service/mii/mii_util.h
+++ b/src/core/hle/service/mii/mii_util.h
@@ -28,6 +28,32 @@ public:
28 return Common::swap16(static_cast<u16>(crc)); 28 return Common::swap16(static_cast<u16>(crc));
29 } 29 }
30 30
31 static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
32 constexpr u16 magic{0x1021};
33 s32 crc{};
34
35 for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
36 for (std::size_t j = 0; j < 8; j++) {
37 crc <<= 1;
38 if ((crc & 0x10000) != 0) {
39 crc = crc ^ magic;
40 }
41 }
42 crc ^= uuid.uuid[i];
43 }
44
45 // As much as this looks wrong this is what N's does
46
47 for (std::size_t i = 0; i < data_size * 8; i++) {
48 crc <<= 1;
49 if ((crc & 0x10000) != 0) {
50 crc = crc ^ magic;
51 }
52 }
53
54 return Common::swap16(static_cast<u16>(crc));
55 }
56
31 static Common::UUID MakeCreateId() { 57 static Common::UUID MakeCreateId() {
32 return Common::UUID::MakeRandomRFC4122V4(); 58 return Common::UUID::MakeRandomRFC4122V4();
33 } 59 }
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp
index bb948c628..e90124af4 100644
--- a/src/core/hle/service/mii/types/char_info.cpp
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -37,7 +37,7 @@ void CharInfo::SetFromStoreData(const StoreData& store_data) {
37 eyebrow_aspect = store_data.GetEyebrowAspect(); 37 eyebrow_aspect = store_data.GetEyebrowAspect();
38 eyebrow_rotate = store_data.GetEyebrowRotate(); 38 eyebrow_rotate = store_data.GetEyebrowRotate();
39 eyebrow_x = store_data.GetEyebrowX(); 39 eyebrow_x = store_data.GetEyebrowX();
40 eyebrow_y = store_data.GetEyebrowY(); 40 eyebrow_y = store_data.GetEyebrowY() + 3;
41 nose_type = store_data.GetNoseType(); 41 nose_type = store_data.GetNoseType();
42 nose_scale = store_data.GetNoseScale(); 42 nose_scale = store_data.GetNoseScale();
43 nose_y = store_data.GetNoseY(); 43 nose_y = store_data.GetNoseY();
@@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const {
150 if (eyebrow_x > MaxEyebrowX) { 150 if (eyebrow_x > MaxEyebrowX) {
151 return ValidationResult::InvalidEyebrowX; 151 return ValidationResult::InvalidEyebrowX;
152 } 152 }
153 if (eyebrow_y > MaxEyebrowY) { 153 if (eyebrow_y - 3 > MaxEyebrowY) {
154 return ValidationResult::InvalidEyebrowY; 154 return ValidationResult::InvalidEyebrowY;
155 } 155 }
156 if (nose_type > NoseType::Max) { 156 if (nose_type > NoseType::Max) {
@@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const {
189 if (mustache_scale > MaxMustacheScale) { 189 if (mustache_scale > MaxMustacheScale) {
190 return ValidationResult::InvalidMustacheScale; 190 return ValidationResult::InvalidMustacheScale;
191 } 191 }
192 if (mustache_y > MasMustacheY) { 192 if (mustache_y > MaxMustacheY) {
193 return ValidationResult::InvalidMustacheY; 193 return ValidationResult::InvalidMustacheY;
194 } 194 }
195 if (glass_type > GlassType::Max) { 195 if (glass_type > GlassType::Max) {
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h
index d069b221f..d0c457fd5 100644
--- a/src/core/hle/service/mii/types/char_info.h
+++ b/src/core/hle/service/mii/types/char_info.h
@@ -70,59 +70,59 @@ public:
70 bool operator==(const CharInfo& info); 70 bool operator==(const CharInfo& info);
71 71
72private: 72private:
73 Common::UUID create_id; 73 Common::UUID create_id{};
74 Nickname name; 74 Nickname name{};
75 u16 null_terminator; 75 u16 null_terminator{};
76 FontRegion font_region; 76 FontRegion font_region{};
77 FavoriteColor favorite_color; 77 FavoriteColor favorite_color{};
78 Gender gender; 78 Gender gender{};
79 u8 height; 79 u8 height{};
80 u8 build; 80 u8 build{};
81 u8 type; 81 u8 type{};
82 u8 region_move; 82 u8 region_move{};
83 FacelineType faceline_type; 83 FacelineType faceline_type{};
84 FacelineColor faceline_color; 84 FacelineColor faceline_color{};
85 FacelineWrinkle faceline_wrinkle; 85 FacelineWrinkle faceline_wrinkle{};
86 FacelineMake faceline_make; 86 FacelineMake faceline_make{};
87 HairType hair_type; 87 HairType hair_type{};
88 CommonColor hair_color; 88 CommonColor hair_color{};
89 HairFlip hair_flip; 89 HairFlip hair_flip{};
90 EyeType eye_type; 90 EyeType eye_type{};
91 CommonColor eye_color; 91 CommonColor eye_color{};
92 u8 eye_scale; 92 u8 eye_scale{};
93 u8 eye_aspect; 93 u8 eye_aspect{};
94 u8 eye_rotate; 94 u8 eye_rotate{};
95 u8 eye_x; 95 u8 eye_x{};
96 u8 eye_y; 96 u8 eye_y{};
97 EyebrowType eyebrow_type; 97 EyebrowType eyebrow_type{};
98 CommonColor eyebrow_color; 98 CommonColor eyebrow_color{};
99 u8 eyebrow_scale; 99 u8 eyebrow_scale{};
100 u8 eyebrow_aspect; 100 u8 eyebrow_aspect{};
101 u8 eyebrow_rotate; 101 u8 eyebrow_rotate{};
102 u8 eyebrow_x; 102 u8 eyebrow_x{};
103 u8 eyebrow_y; 103 u8 eyebrow_y{};
104 NoseType nose_type; 104 NoseType nose_type{};
105 u8 nose_scale; 105 u8 nose_scale{};
106 u8 nose_y; 106 u8 nose_y{};
107 MouthType mouth_type; 107 MouthType mouth_type{};
108 CommonColor mouth_color; 108 CommonColor mouth_color{};
109 u8 mouth_scale; 109 u8 mouth_scale{};
110 u8 mouth_aspect; 110 u8 mouth_aspect{};
111 u8 mouth_y; 111 u8 mouth_y{};
112 CommonColor beard_color; 112 CommonColor beard_color{};
113 BeardType beard_type; 113 BeardType beard_type{};
114 MustacheType mustache_type; 114 MustacheType mustache_type{};
115 u8 mustache_scale; 115 u8 mustache_scale{};
116 u8 mustache_y; 116 u8 mustache_y{};
117 GlassType glass_type; 117 GlassType glass_type{};
118 CommonColor glass_color; 118 CommonColor glass_color{};
119 u8 glass_scale; 119 u8 glass_scale{};
120 u8 glass_y; 120 u8 glass_y{};
121 MoleType mole_type; 121 MoleType mole_type{};
122 u8 mole_scale; 122 u8 mole_scale{};
123 u8 mole_x; 123 u8 mole_x{};
124 u8 mole_y; 124 u8 mole_y{};
125 u8 padding; 125 u8 padding{};
126}; 126};
127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); 127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
128static_assert(std::has_unique_object_representations_v<CharInfo>, 128static_assert(std::has_unique_object_representations_v<CharInfo>,
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
index 659288b51..970c748ca 100644
--- a/src/core/hle/service/mii/types/core_data.cpp
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -3,6 +3,7 @@
3 3
4#include "common/assert.h" 4#include "common/assert.h"
5#include "core/hle/service/mii/mii_util.h" 5#include "core/hle/service/mii/mii_util.h"
6#include "core/hle/service/mii/types/char_info.h"
6#include "core/hle/service/mii/types/core_data.h" 7#include "core/hle/service/mii/types/core_data.h"
7#include "core/hle/service/mii/types/raw_data.h" 8#include "core/hle/service/mii/types/raw_data.h"
8 9
@@ -112,7 +113,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
112 .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); 113 .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
113 114
114 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; 115 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
115 const auto eyebrow_y{race == Race::Asian ? 9 : 10}; 116 const auto eyebrow_y{race == Race::Asian ? 6 : 7};
116 const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; 117 const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
117 const auto eyebrow_rotate{ 118 const auto eyebrow_rotate{
118 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; 119 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
@@ -170,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
170 u8 glasses_type{}; 171 u8 glasses_type{};
171 while (glasses_type_start < glasses_type_info.values[glasses_type]) { 172 while (glasses_type_start < glasses_type_info.values[glasses_type]) {
172 if (++glasses_type >= glasses_type_info.values_count) { 173 if (++glasses_type >= glasses_type_info.values_count) {
173 ASSERT(false); 174 glasses_type = 0;
174 break; 175 break;
175 } 176 }
176 } 177 }
@@ -178,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
178 SetGlassType(static_cast<GlassType>(glasses_type)); 179 SetGlassType(static_cast<GlassType>(glasses_type));
179 SetGlassColor(RawData::GetGlassColorFromVer3(0)); 180 SetGlassColor(RawData::GetGlassColorFromVer3(0));
180 SetGlassScale(4); 181 SetGlassScale(4);
182 SetGlassY(static_cast<u8>(axis_y + 10));
181 183
182 SetMoleType(MoleType::None); 184 SetMoleType(MoleType::None);
183 SetMoleScale(4); 185 SetMoleScale(4);
@@ -185,9 +187,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
185 SetMoleY(20); 187 SetMoleY(20);
186} 188}
187 189
188u32 CoreData::IsValid() const { 190void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
189 // TODO: Complete this 191 name = char_info.GetNickname();
190 return 0; 192 SetFontRegion(char_info.GetFontRegion());
193 SetFavoriteColor(char_info.GetFavoriteColor());
194 SetGender(char_info.GetGender());
195 SetHeight(char_info.GetHeight());
196 SetBuild(char_info.GetBuild());
197 SetType(char_info.GetType());
198 SetRegionMove(char_info.GetRegionMove());
199 SetFacelineType(char_info.GetFacelineType());
200 SetFacelineColor(char_info.GetFacelineColor());
201 SetFacelineWrinkle(char_info.GetFacelineWrinkle());
202 SetFacelineMake(char_info.GetFacelineMake());
203 SetHairType(char_info.GetHairType());
204 SetHairColor(char_info.GetHairColor());
205 SetHairFlip(char_info.GetHairFlip());
206 SetEyeType(char_info.GetEyeType());
207 SetEyeColor(char_info.GetEyeColor());
208 SetEyeScale(char_info.GetEyeScale());
209 SetEyeAspect(char_info.GetEyeAspect());
210 SetEyeRotate(char_info.GetEyeRotate());
211 SetEyeX(char_info.GetEyeX());
212 SetEyeY(char_info.GetEyeY());
213 SetEyebrowType(char_info.GetEyebrowType());
214 SetEyebrowColor(char_info.GetEyebrowColor());
215 SetEyebrowScale(char_info.GetEyebrowScale());
216 SetEyebrowAspect(char_info.GetEyebrowAspect());
217 SetEyebrowRotate(char_info.GetEyebrowRotate());
218 SetEyebrowX(char_info.GetEyebrowX());
219 SetEyebrowY(char_info.GetEyebrowY() - 3);
220 SetNoseType(char_info.GetNoseType());
221 SetNoseScale(char_info.GetNoseScale());
222 SetNoseY(char_info.GetNoseY());
223 SetMouthType(char_info.GetMouthType());
224 SetMouthColor(char_info.GetMouthColor());
225 SetMouthScale(char_info.GetMouthScale());
226 SetMouthAspect(char_info.GetMouthAspect());
227 SetMouthY(char_info.GetMouthY());
228 SetBeardColor(char_info.GetBeardColor());
229 SetBeardType(char_info.GetBeardType());
230 SetMustacheType(char_info.GetMustacheType());
231 SetMustacheScale(char_info.GetMustacheScale());
232 SetMustacheY(char_info.GetMustacheY());
233 SetGlassType(char_info.GetGlassType());
234 SetGlassColor(char_info.GetGlassColor());
235 SetGlassScale(char_info.GetGlassScale());
236 SetGlassY(char_info.GetGlassY());
237 SetMoleType(char_info.GetMoleType());
238 SetMoleScale(char_info.GetMoleScale());
239 SetMoleX(char_info.GetMoleX());
240 SetMoleY(char_info.GetMoleY());
241}
242
243ValidationResult CoreData::IsValid() const {
244 if (!name.IsValid()) {
245 return ValidationResult::InvalidName;
246 }
247 if (GetFontRegion() > FontRegion::Max) {
248 return ValidationResult::InvalidFont;
249 }
250 if (GetFavoriteColor() > FavoriteColor::Max) {
251 return ValidationResult::InvalidColor;
252 }
253 if (GetGender() > Gender::Max) {
254 return ValidationResult::InvalidGender;
255 }
256 if (GetHeight() > MaxHeight) {
257 return ValidationResult::InvalidHeight;
258 }
259 if (GetBuild() > MaxBuild) {
260 return ValidationResult::InvalidBuild;
261 }
262 if (GetType() > MaxType) {
263 return ValidationResult::InvalidType;
264 }
265 if (GetRegionMove() > MaxRegionMove) {
266 return ValidationResult::InvalidRegionMove;
267 }
268 if (GetFacelineType() > FacelineType::Max) {
269 return ValidationResult::InvalidFacelineType;
270 }
271 if (GetFacelineColor() > FacelineColor::Max) {
272 return ValidationResult::InvalidFacelineColor;
273 }
274 if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
275 return ValidationResult::InvalidFacelineWrinkle;
276 }
277 if (GetFacelineMake() > FacelineMake::Max) {
278 return ValidationResult::InvalidFacelineMake;
279 }
280 if (GetHairType() > HairType::Max) {
281 return ValidationResult::InvalidHairType;
282 }
283 if (GetHairColor() > CommonColor::Max) {
284 return ValidationResult::InvalidHairColor;
285 }
286 if (GetHairFlip() > HairFlip::Max) {
287 return ValidationResult::InvalidHairFlip;
288 }
289 if (GetEyeType() > EyeType::Max) {
290 return ValidationResult::InvalidEyeType;
291 }
292 if (GetEyeColor() > CommonColor::Max) {
293 return ValidationResult::InvalidEyeColor;
294 }
295 if (GetEyeScale() > MaxEyeScale) {
296 return ValidationResult::InvalidEyeScale;
297 }
298 if (GetEyeAspect() > MaxEyeAspect) {
299 return ValidationResult::InvalidEyeAspect;
300 }
301 if (GetEyeRotate() > MaxEyeRotate) {
302 return ValidationResult::InvalidEyeRotate;
303 }
304 if (GetEyeX() > MaxEyeX) {
305 return ValidationResult::InvalidEyeX;
306 }
307 if (GetEyeY() > MaxEyeY) {
308 return ValidationResult::InvalidEyeY;
309 }
310 if (GetEyebrowType() > EyebrowType::Max) {
311 return ValidationResult::InvalidEyebrowType;
312 }
313 if (GetEyebrowColor() > CommonColor::Max) {
314 return ValidationResult::InvalidEyebrowColor;
315 }
316 if (GetEyebrowScale() > MaxEyebrowScale) {
317 return ValidationResult::InvalidEyebrowScale;
318 }
319 if (GetEyebrowAspect() > MaxEyebrowAspect) {
320 return ValidationResult::InvalidEyebrowAspect;
321 }
322 if (GetEyebrowRotate() > MaxEyebrowRotate) {
323 return ValidationResult::InvalidEyebrowRotate;
324 }
325 if (GetEyebrowX() > MaxEyebrowX) {
326 return ValidationResult::InvalidEyebrowX;
327 }
328 if (GetEyebrowY() > MaxEyebrowY) {
329 return ValidationResult::InvalidEyebrowY;
330 }
331 if (GetNoseType() > NoseType::Max) {
332 return ValidationResult::InvalidNoseType;
333 }
334 if (GetNoseScale() > MaxNoseScale) {
335 return ValidationResult::InvalidNoseScale;
336 }
337 if (GetNoseY() > MaxNoseY) {
338 return ValidationResult::InvalidNoseY;
339 }
340 if (GetMouthType() > MouthType::Max) {
341 return ValidationResult::InvalidMouthType;
342 }
343 if (GetMouthColor() > CommonColor::Max) {
344 return ValidationResult::InvalidMouthColor;
345 }
346 if (GetMouthScale() > MaxMouthScale) {
347 return ValidationResult::InvalidMouthScale;
348 }
349 if (GetMouthAspect() > MaxMoutAspect) {
350 return ValidationResult::InvalidMouthAspect;
351 }
352 if (GetMouthY() > MaxMouthY) {
353 return ValidationResult::InvalidMouthY;
354 }
355 if (GetBeardColor() > CommonColor::Max) {
356 return ValidationResult::InvalidBeardColor;
357 }
358 if (GetBeardType() > BeardType::Max) {
359 return ValidationResult::InvalidBeardType;
360 }
361 if (GetMustacheType() > MustacheType::Max) {
362 return ValidationResult::InvalidMustacheType;
363 }
364 if (GetMustacheScale() > MaxMustacheScale) {
365 return ValidationResult::InvalidMustacheScale;
366 }
367 if (GetMustacheY() > MaxMustacheY) {
368 return ValidationResult::InvalidMustacheY;
369 }
370 if (GetGlassType() > GlassType::Max) {
371 return ValidationResult::InvalidGlassType;
372 }
373 if (GetGlassColor() > CommonColor::Max) {
374 return ValidationResult::InvalidGlassColor;
375 }
376 if (GetGlassScale() > MaxGlassScale) {
377 return ValidationResult::InvalidGlassScale;
378 }
379 if (GetGlassY() > MaxGlassY) {
380 return ValidationResult::InvalidGlassY;
381 }
382 if (GetMoleType() > MoleType::Max) {
383 return ValidationResult::InvalidMoleType;
384 }
385 if (GetMoleScale() > MaxMoleScale) {
386 return ValidationResult::InvalidMoleScale;
387 }
388 if (GetMoleX() > MaxMoleX) {
389 return ValidationResult::InvalidMoleX;
390 }
391 if (GetMoleY() > MaxMoleY) {
392 return ValidationResult::InvalidMoleY;
393 }
394 return ValidationResult::NoErrors;
191} 395}
192 396
193void CoreData::SetFontRegion(FontRegion value) { 397void CoreData::SetFontRegion(FontRegion value) {
@@ -314,8 +518,8 @@ void CoreData::SetNoseY(u8 value) {
314 data.nose_y.Assign(value); 518 data.nose_y.Assign(value);
315} 519}
316 520
317void CoreData::SetMouthType(u8 value) { 521void CoreData::SetMouthType(MouthType value) {
318 data.mouth_type.Assign(value); 522 data.mouth_type.Assign(static_cast<u32>(value));
319} 523}
320 524
321void CoreData::SetMouthColor(CommonColor value) { 525void CoreData::SetMouthColor(CommonColor value) {
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h
index cebcd2ee4..8897e4f3b 100644
--- a/src/core/hle/service/mii/types/core_data.h
+++ b/src/core/hle/service/mii/types/core_data.h
@@ -6,6 +6,7 @@
6#include "core/hle/service/mii/mii_types.h" 6#include "core/hle/service/mii/mii_types.h"
7 7
8namespace Service::Mii { 8namespace Service::Mii {
9class CharInfo;
9 10
10struct StoreDataBitFields { 11struct StoreDataBitFields {
11 union { 12 union {
@@ -100,8 +101,9 @@ class CoreData {
100public: 101public:
101 void SetDefault(); 102 void SetDefault();
102 void BuildRandom(Age age, Gender gender, Race race); 103 void BuildRandom(Age age, Gender gender, Race race);
104 void BuildFromCharInfo(const CharInfo& char_info);
103 105
104 u32 IsValid() const; 106 ValidationResult IsValid() const;
105 107
106 void SetFontRegion(FontRegion value); 108 void SetFontRegion(FontRegion value);
107 void SetFavoriteColor(FavoriteColor value); 109 void SetFavoriteColor(FavoriteColor value);
@@ -134,7 +136,7 @@ public:
134 void SetNoseType(NoseType value); 136 void SetNoseType(NoseType value);
135 void SetNoseScale(u8 value); 137 void SetNoseScale(u8 value);
136 void SetNoseY(u8 value); 138 void SetNoseY(u8 value);
137 void SetMouthType(u8 value); 139 void SetMouthType(MouthType value);
138 void SetMouthColor(CommonColor value); 140 void SetMouthColor(CommonColor value);
139 void SetMouthScale(u8 value); 141 void SetMouthScale(u8 value);
140 void SetMouthAspect(u8 value); 142 void SetMouthAspect(u8 value);
@@ -212,5 +214,6 @@ private:
212 Nickname name{}; 214 Nickname name{};
213}; 215};
214static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); 216static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
217static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
215 218
216}; // namespace Service::Mii 219}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp
index 5143abcc8..0e1a07fd7 100644
--- a/src/core/hle/service/mii/types/raw_data.cpp
+++ b/src/core/hle/service/mii/types/raw_data.cpp
@@ -1716,18 +1716,18 @@ const std::array<RandomMiiData4, 18> RandomMiiMouthType{
1716const std::array<RandomMiiData2, 3> RandomMiiGlassType{ 1716const std::array<RandomMiiData2, 3> RandomMiiGlassType{
1717 RandomMiiData2{ 1717 RandomMiiData2{
1718 .arg_1 = 0, 1718 .arg_1 = 0,
1719 .values_count = 9, 1719 .values_count = 4,
1720 .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, 1720 .values = {90, 94, 96, 100},
1721 }, 1721 },
1722 RandomMiiData2{ 1722 RandomMiiData2{
1723 .arg_1 = 1, 1723 .arg_1 = 1,
1724 .values_count = 9, 1724 .values_count = 8,
1725 .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, 1725 .values = {83, 86, 90, 93, 94, 96, 98, 100},
1726 }, 1726 },
1727 RandomMiiData2{ 1727 RandomMiiData2{
1728 .arg_1 = 2, 1728 .arg_1 = 2,
1729 .values_count = 9, 1729 .values_count = 8,
1730 .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, 1730 .values = {78, 83, 0, 93, 0, 0, 98, 100},
1731 }, 1731 },
1732}; 1732};
1733 1733
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp
index 8fce636c7..127221fdb 100644
--- a/src/core/hle/service/mii/types/store_data.cpp
+++ b/src/core/hle/service/mii/types/store_data.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 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#include "core/hle/service/mii/mii_result.h"
4#include "core/hle/service/mii/mii_util.h" 5#include "core/hle/service/mii/mii_util.h"
5#include "core/hle/service/mii/types/raw_data.h" 6#include "core/hle/service/mii/types/raw_data.h"
6#include "core/hle/service/mii/types/store_data.h" 7#include "core/hle/service/mii/types/store_data.h"
@@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) {
35 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); 36 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
36 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); 37 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
37 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); 38 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
38 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); 39 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
39 40
40 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); 41 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
41 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); 42 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
42 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); 43 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
43 44
44 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); 45 core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
45 core_data.SetMouthColor( 46 core_data.SetMouthColor(
46 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); 47 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
47 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); 48 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) {
75 core_data.SetType(static_cast<u8>(default_mii.type)); 76 core_data.SetType(static_cast<u8>(default_mii.type));
76 core_data.SetNickname(default_mii.nickname); 77 core_data.SetNickname(default_mii.nickname);
77 78
78 const auto device_id = MiiUtil::GetDeviceId();
79 create_id = MiiUtil::MakeCreateId(); 79 create_id = MiiUtil::MakeCreateId();
80 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 80 SetChecksum();
81 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
82} 81}
83 82
84void StoreData::BuildBase(Gender gender) { 83void StoreData::BuildBase(Gender gender) {
@@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) {
109 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); 108 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
110 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); 109 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
111 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); 110 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
112 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); 111 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
113 112
114 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); 113 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
115 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); 114 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
116 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); 115 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
117 116
118 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); 117 core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
119 core_data.SetMouthColor( 118 core_data.SetMouthColor(
120 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); 119 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
121 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); 120 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) {
149 core_data.SetType(static_cast<u8>(default_mii.type)); 148 core_data.SetType(static_cast<u8>(default_mii.type));
150 core_data.SetNickname(default_mii.nickname); 149 core_data.SetNickname(default_mii.nickname);
151 150
152 const auto device_id = MiiUtil::GetDeviceId();
153 create_id = MiiUtil::MakeCreateId(); 151 create_id = MiiUtil::MakeCreateId();
154 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 152 SetChecksum();
155 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
156} 153}
157 154
158void StoreData::BuildRandom(Age age, Gender gender, Race race) { 155void StoreData::BuildRandom(Age age, Gender gender, Race race) {
159 core_data.BuildRandom(age, gender, race); 156 core_data.BuildRandom(age, gender, race);
160 const auto device_id = MiiUtil::GetDeviceId();
161 create_id = MiiUtil::MakeCreateId(); 157 create_id = MiiUtil::MakeCreateId();
162 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 158 SetChecksum();
163 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
164} 159}
165 160
166void StoreData::SetInvalidName() { 161void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
167 const auto& invalid_name = core_data.GetInvalidNickname(); 162 core_data.BuildFromCharInfo(char_info);
163 create_id = MiiUtil::MakeCreateId();
164 SetChecksum();
165}
166
167void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
168 core_data = in_core_data;
169 create_id = MiiUtil::MakeCreateId();
170 SetChecksum();
171}
172
173Result StoreData::Restore() {
174 // TODO: Implement this
175 return ResultNotUpdated;
176}
177
178ValidationResult StoreData::IsValid() const {
179 if (core_data.IsValid() != ValidationResult::NoErrors) {
180 return core_data.IsValid();
181 }
182 if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
183 return ValidationResult::InvalidChecksum;
184 }
168 const auto device_id = MiiUtil::GetDeviceId(); 185 const auto device_id = MiiUtil::GetDeviceId();
169 core_data.SetNickname(invalid_name); 186 if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
170 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 187 return ValidationResult::InvalidChecksum;
171 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); 188 }
189 return ValidationResult::NoErrors;
172} 190}
173 191
174bool StoreData::IsSpecial() const { 192bool StoreData::IsSpecial() const {
175 return GetType() == 1; 193 return GetType() == 1;
176} 194}
177 195
178u32 StoreData::IsValid() const {
179 // TODO: complete this
180 return 0;
181}
182
183void StoreData::SetFontRegion(FontRegion value) { 196void StoreData::SetFontRegion(FontRegion value) {
184 core_data.SetFontRegion(value); 197 core_data.SetFontRegion(value);
185} 198}
@@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) {
304 core_data.SetNoseY(value); 317 core_data.SetNoseY(value);
305} 318}
306 319
307void StoreData::SetMouthType(u8 value) { 320void StoreData::SetMouthType(MouthType value) {
308 core_data.SetMouthType(value); 321 core_data.SetMouthType(value);
309} 322}
310 323
@@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) {
380 core_data.SetNickname(value); 393 core_data.SetNickname(value);
381} 394}
382 395
396void StoreData::SetInvalidName() {
397 const auto& invalid_name = core_data.GetInvalidNickname();
398 core_data.SetNickname(invalid_name);
399 SetChecksum();
400}
401
402void StoreData::SetChecksum() {
403 SetDataChecksum();
404 SetDeviceChecksum();
405}
406
407void StoreData::SetDataChecksum() {
408 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
409}
410
411void StoreData::SetDeviceChecksum() {
412 const auto device_id = MiiUtil::GetDeviceId();
413 device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
414}
415
383Common::UUID StoreData::GetCreateId() const { 416Common::UUID StoreData::GetCreateId() const {
384 return create_id; 417 return create_id;
385} 418}
@@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const {
585} 618}
586 619
587bool StoreData::operator==(const StoreData& data) { 620bool StoreData::operator==(const StoreData& data) {
588 bool is_identical = data.core_data.IsValid() == 0; 621 bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
589 is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; 622 is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
590 is_identical &= GetCreateId() == data.GetCreateId(); 623 is_identical &= GetCreateId() == data.GetCreateId();
591 is_identical &= GetFontRegion() == data.GetFontRegion(); 624 is_identical &= GetFontRegion() == data.GetFontRegion();
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h
index 224c32cf8..ed5dfb949 100644
--- a/src/core/hle/service/mii/types/store_data.h
+++ b/src/core/hle/service/mii/types/store_data.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/result.h"
6#include "core/hle/service/mii/mii_types.h" 7#include "core/hle/service/mii/mii_types.h"
7#include "core/hle/service/mii/types/core_data.h" 8#include "core/hle/service/mii/types/core_data.h"
8 9
@@ -10,17 +11,16 @@ namespace Service::Mii {
10 11
11class StoreData { 12class StoreData {
12public: 13public:
13 // nn::mii::detail::StoreDataRaw::BuildDefault
14 void BuildDefault(u32 mii_index); 14 void BuildDefault(u32 mii_index);
15 // nn::mii::detail::StoreDataRaw::BuildDefault
16
17 void BuildBase(Gender gender); 15 void BuildBase(Gender gender);
18 // nn::mii::detail::StoreDataRaw::BuildRandom
19 void BuildRandom(Age age, Gender gender, Race race); 16 void BuildRandom(Age age, Gender gender, Race race);
17 void BuildWithCharInfo(const CharInfo& char_info);
18 void BuildWithCoreData(const CoreData& in_core_data);
19 Result Restore();
20 20
21 bool IsSpecial() const; 21 ValidationResult IsValid() const;
22 22
23 u32 IsValid() const; 23 bool IsSpecial() const;
24 24
25 void SetFontRegion(FontRegion value); 25 void SetFontRegion(FontRegion value);
26 void SetFavoriteColor(FavoriteColor value); 26 void SetFavoriteColor(FavoriteColor value);
@@ -53,7 +53,7 @@ public:
53 void SetNoseType(NoseType value); 53 void SetNoseType(NoseType value);
54 void SetNoseScale(u8 value); 54 void SetNoseScale(u8 value);
55 void SetNoseY(u8 value); 55 void SetNoseY(u8 value);
56 void SetMouthType(u8 value); 56 void SetMouthType(MouthType value);
57 void SetMouthColor(CommonColor value); 57 void SetMouthColor(CommonColor value);
58 void SetMouthScale(u8 value); 58 void SetMouthScale(u8 value);
59 void SetMouthAspect(u8 value); 59 void SetMouthAspect(u8 value);
@@ -73,6 +73,9 @@ public:
73 void SetMoleY(u8 value); 73 void SetMoleY(u8 value);
74 void SetNickname(Nickname nickname); 74 void SetNickname(Nickname nickname);
75 void SetInvalidName(); 75 void SetInvalidName();
76 void SetChecksum();
77 void SetDataChecksum();
78 void SetDeviceChecksum();
76 79
77 Common::UUID GetCreateId() const; 80 Common::UUID GetCreateId() const;
78 FontRegion GetFontRegion() const; 81 FontRegion GetFontRegion() const;
@@ -135,6 +138,8 @@ private:
135 u16 device_crc{}; 138 u16 device_crc{};
136}; 139};
137static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); 140static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
141static_assert(std::is_trivially_copyable_v<StoreData>,
142 "StoreData type must be trivially copyable.");
138 143
139struct StoreDataElement { 144struct StoreDataElement {
140 StoreData store_data{}; 145 StoreData store_data{};
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index 1c28e0b1b..a019cc9f7 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
22void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { 22void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
23 out_store_data.BuildBase(Gender::Male); 23 out_store_data.BuildBase(Gender::Male);
24 24
25 if (!IsValid()) {
26 return;
27 }
28
29 // TODO: We are ignoring a bunch of data from the mii_v3
30
31 out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); 25 out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
32 out_store_data.SetFavoriteColor( 26 out_store_data.SetFavoriteColor(
33 static_cast<FavoriteColor>(mii_information.favorite_color.Value())); 27 static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
@@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
36 30
37 out_store_data.SetNickname(mii_name); 31 out_store_data.SetNickname(mii_name);
38 out_store_data.SetFontRegion( 32 out_store_data.SetFontRegion(
39 static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); 33 static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
40 34
41 out_store_data.SetFacelineType( 35 out_store_data.SetFacelineType(
42 static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); 36 static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
43 out_store_data.SetFacelineColor( 37 out_store_data.SetFacelineColor(
44 static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); 38 RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value()));
45 out_store_data.SetFacelineWrinkle( 39 out_store_data.SetFacelineWrinkle(
46 static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); 40 static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
47 out_store_data.SetFacelineMake( 41 out_store_data.SetFacelineMake(
48 static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); 42 static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
49 43
50 out_store_data.SetHairType(static_cast<HairType>(hair_type)); 44 out_store_data.SetHairType(static_cast<HairType>(hair_type));
51 out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); 45 out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value()));
52 out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); 46 out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
53 47
54 out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); 48 out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
55 out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); 49 out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
56 out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); 50 out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
57 out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); 51 out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
58 out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); 52 out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
59 out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); 53 out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
60 out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); 54 out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
61 55
62 out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); 56 out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
63 out_store_data.SetEyebrowColor( 57 out_store_data.SetEyebrowColor(
64 static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); 58 RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
65 out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); 59 out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
66 out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); 60 out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
67 out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); 61 out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
68 out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); 62 out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
69 out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); 63 out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
70 64
71 out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); 65 out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
72 out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); 66 out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
73 out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); 67 out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
74 68
75 out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); 69 out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
76 out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); 70 out_store_data.SetMouthColor(
77 out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); 71 RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
78 out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); 72 out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
79 out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); 73 out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
74 out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
80 75
81 out_store_data.SetMustacheType( 76 out_store_data.SetMustacheType(
82 static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); 77 static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
83 out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); 78 out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
84 out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); 79 out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
85 80
86 out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); 81 out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
87 out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); 82 out_store_data.SetBeardColor(
83 RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
88 84
85 // Glass type is compatible as it is. It doesn't need a table
89 out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); 86 out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
90 out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); 87 out_store_data.SetGlassColor(
91 out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); 88 RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
92 out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); 89 out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
90 out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
93 91
94 out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); 92 out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
95 out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); 93 out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
96 out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); 94 out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
97 out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); 95 out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
96
97 out_store_data.SetChecksum();
98} 98}
99 99
100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { 100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
@@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const {
220 220
221 is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); 221 is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
222 is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); 222 is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
223 is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); 223 is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY);
224 224
225 is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); 225 is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
226 is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); 226 is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
@@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const {
228 is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); 228 is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
229 is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); 229 is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
230 is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); 230 is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
231 is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); 231 is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY);
232 232
233 is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); 233 is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
234 is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); 234 is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 5dda12343..68c407f81 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
439 439
440 device_state = DeviceState::TagMounted; 440 device_state = DeviceState::TagMounted;
441 mount_target = mount_target_; 441 mount_target = mount_target_;
442
442 return ResultSuccess; 443 return ResultSuccess;
443} 444}
444 445
@@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
716 return ResultRegistrationIsNotInitialized; 717 return ResultRegistrationIsNotInitialized;
717 } 718 }
718 719
719 Service::Mii::MiiManager manager; 720 Mii::StoreData store_data{};
720 const auto& settings = tag_data.settings; 721 const auto& settings = tag_data.settings;
722 tag_data.owner_mii.BuildToStoreData(store_data);
721 723
722 // TODO: Validate and complete this data 724 // TODO: Validate and complete this data
723 register_info = { 725 register_info = {
724 .mii_store_data = {}, 726 .mii_store_data = store_data,
725 .creation_date = settings.init_date.GetWriteDate(), 727 .creation_date = settings.init_date.GetWriteDate(),
726 .amiibo_name = GetAmiiboName(settings), 728 .amiibo_name = GetAmiiboName(settings),
727 .font_region = settings.settings.font_region, 729 .font_region = settings.settings.font_region,
@@ -874,17 +876,19 @@ Result NfcDevice::RestoreAmiibo() {
874} 876}
875 877
876Result NfcDevice::Format() { 878Result NfcDevice::Format() {
877 auto result1 = DeleteApplicationArea(); 879 Result result = ResultSuccess;
878 auto result2 = DeleteRegisterInfo();
879 880
880 if (result1.IsError()) { 881 if (device_state == DeviceState::TagFound) {
881 return result1; 882 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
882 } 883 }
883 884
884 if (result2.IsError()) { 885 if (result.IsError()) {
885 return result2; 886 return result;
886 } 887 }
887 888
889 DeleteApplicationArea();
890 DeleteRegisterInfo();
891
888 return Flush(); 892 return Flush();
889} 893}
890 894
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp
index 6c2f5e70b..46268be95 100644
--- a/src/core/hle/service/ns/iplatform_service_manager.cpp
+++ b/src/core/hle/service/ns/iplatform_service_manager.cpp
@@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
144 {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, 144 {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
145 {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, 145 {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
146 {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, 146 {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
147 {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, 147 {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"},
148 {100, nullptr, "RequestApplicationFunctionAuthorization"}, 148 {100, nullptr, "RequestApplicationFunctionAuthorization"},
149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, 149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
150 {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"}, 150 {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
@@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx
262} 262}
263 263
264void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) { 264void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) {
265 // The maximum number of elements that can be returned is 6. Regardless of the available fonts
266 // or buffer size.
267 constexpr std::size_t MaxElementCount = 6;
265 IPC::RequestParser rp{ctx}; 268 IPC::RequestParser rp{ctx};
266 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for 269 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
270 const std::size_t font_codes_count =
271 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0));
272 const std::size_t font_offsets_count =
273 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1));
274 const std::size_t font_sizes_count =
275 std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2));
267 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); 276 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
268 277
269 IPC::ResponseBuilder rb{ctx, 4}; 278 IPC::ResponseBuilder rb{ctx, 4};
@@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext&
280 } 289 }
281 290
282 // Resize buffers if game requests smaller size output 291 // Resize buffers if game requests smaller size output
283 font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0))); 292 font_codes.resize(std::min(font_codes.size(), font_codes_count));
284 font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1))); 293 font_offsets.resize(std::min(font_offsets.size(), font_offsets_count));
285 font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2))); 294 font_sizes.resize(std::min(font_sizes.size(), font_sizes_count));
286 295
287 ctx.WriteBuffer(font_codes, 0); 296 ctx.WriteBuffer(font_codes, 0);
288 ctx.WriteBuffer(font_offsets, 1); 297 ctx.WriteBuffer(font_offsets, 1);
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index d8509c1dd..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
170} 170}
171 171
172void BSD::Select(HLERequestContext& ctx) { 172void BSD::Select(HLERequestContext& ctx) {
173 LOG_WARNING(Service, "(STUBBED) called"); 173 LOG_DEBUG(Service, "(STUBBED) called");
174 174
175 IPC::ResponseBuilder rb{ctx, 4}; 175 IPC::ResponseBuilder rb{ctx, 4};
176 176
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index f4eaf3331..5a42dea48 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -18,7 +18,7 @@ namespace Loader {
18 18
19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, 19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
20 bool override_update_) 20 bool override_update_)
21 : AppLoader(std::move(file_)), override_update(override_update_) { 21 : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
22 const auto file_dir = file->GetContainingDirectory(); 22 const auto file_dir = file->GetContainingDirectory();
23 23
24 // Title ID 24 // Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
69} 69}
70 70
71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
72 FileSys::VirtualDir directory, bool override_update_) 72 FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)), 73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)),
74 override_update(override_update_) {} 74 override_update(override_update_), is_hbl(is_hbl_) {}
75 75
76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { 76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { 77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,7 +147,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
147 } 147 }
148 148
149 // Setup the process code layout 149 // Setup the process code layout
150 if (process.LoadFromMetadata(metadata, code_size).IsError()) { 150 if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; 151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
152 } 152 }
153 153
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index f7702225e..1e9f765c9 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -27,7 +27,8 @@ public:
27 27
28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' 28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, 29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
30 bool override_update_ = false); 30 bool override_update_ = false,
31 bool is_hbl_ = false);
31 32
32 /** 33 /**
33 * Identifies whether or not the given file is a deconstructed ROM directory. 34 * Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
62 std::string name; 63 std::string name;
63 u64 title_id{}; 64 u64 title_id{};
64 bool override_update; 65 bool override_update;
66 bool is_hbl;
65 67
66 Modules modules; 68 Modules modules;
67}; 69};
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index d722459c6..bf56a08b4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -90,7 +90,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
90 codeset.DataSegment().size += kip->GetBSSSize(); 90 codeset.DataSegment().size += kip->GetBSSSize();
91 91
92 // Setup the process code layout 92 // Setup the process code layout
93 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 93 if (process
94 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
94 .IsError()) { 95 .IsError()) {
95 return {ResultStatus::ErrorNotInitialized, {}}; 96 return {ResultStatus::ErrorNotInitialized, {}};
96 } 97 }
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index d7562b4bc..69f1a54ed 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -196,7 +196,8 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size); 196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
197 197
198 // Setup the process code layout 198 // Setup the process code layout
199 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 199 if (process
200 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
200 .IsError()) { 201 .IsError()) {
201 return false; 202 return false;
202 } 203 }
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 549822506..1350da8dc 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
127 } 127 }
128 128
129 // Apply patches if necessary 129 // Apply patches if necessary
130 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { 130 const auto name = nso_file.GetName();
131 if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
131 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); 132 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
132 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); 133 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
133 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), 134 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
134 program_image.size()); 135 program_image.size());
135 136
136 pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); 137 pi_header = pm->PatchNSO(pi_header, name);
137 138
138 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); 139 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
139 } 140 }
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index fe2af1ae6..f4ab75b77 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
30 } 30 }
31 31
32 if (nsp->IsExtractedType()) { 32 if (nsp->IsExtractedType()) {
33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); 33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
34 nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
34 } else { 35 } else {
35 const auto control_nca = 36 const auto control_nca =
36 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); 37 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
154 return {}; 154 return {};
155 } 155 }
156 156
157 const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); 157 const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = 158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
159 value; 159 value;
160 160
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..947fa6cb3
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <renderdoc_app.h>
5
6#include "common/assert.h"
7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h"
9
10#ifdef _WIN32
11#include <windows.h>
12#else
13#include <dlfcn.h>
14#endif
15
16namespace Tools {
17
18RenderdocAPI::RenderdocAPI() {
19#ifdef WIN32
20 if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
21 const auto RENDERDOC_GetAPI =
22 reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
23 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
24 ASSERT(ret == 1);
25 }
26#else
27#ifdef ANDROID
28 static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
29#else
30 static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
31#endif
32 if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
33 const auto RENDERDOC_GetAPI =
34 reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
35 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
36 ASSERT(ret == 1);
37 }
38#endif
39}
40
41RenderdocAPI::~RenderdocAPI() = default;
42
43void RenderdocAPI::ToggleCapture() {
44 if (!rdoc_api) [[unlikely]] {
45 return;
46 }
47 if (!is_capturing) {
48 rdoc_api->StartFrameCapture(NULL, NULL);
49 } else {
50 rdoc_api->EndFrameCapture(NULL, NULL);
51 }
52 is_capturing = !is_capturing;
53}
54
55} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
index 2a12feddc..dde0f6e9c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
@@ -7,15 +7,12 @@
7 7
8namespace Shader::Backend::SPIRV { 8namespace Shader::Backend::SPIRV {
9namespace { 9namespace {
10Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) { 10Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
11 if (!index.IsImmediate()) {
12 throw NotImplementedException("Indirect image indexing");
13 }
14 if (info.type == TextureType::Buffer) { 11 if (info.type == TextureType::Buffer) {
15 const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())}; 12 const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
16 return def.id; 13 return def.id;
17 } else { 14 } else {
18 const ImageDefinition def{ctx.images.at(index.U32())}; 15 const ImageDefinition def{ctx.images.at(info.descriptor_index)};
19 return def.id; 16 return def.id;
20 } 17 }
21} 18}
@@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
28 25
29Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value, 26Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
30 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { 27 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
28 if (!index.IsImmediate() || index.U32() != 0) {
29 // TODO: handle layers
30 throw NotImplementedException("Image indexing");
31 }
31 const auto info{inst->Flags<IR::TextureInstInfo>()}; 32 const auto info{inst->Flags<IR::TextureInstInfo>()};
32 const Id image{Image(ctx, index, info)}; 33 const Id image{Image(ctx, info)};
33 const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))}; 34 const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
34 const auto [scope, semantics]{AtomicArgs(ctx)}; 35 const auto [scope, semantics]{AtomicArgs(ctx)};
35 return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); 36 return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 72f69b7aa..57df6fc34 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
74 throw InvalidArgument("Invalid image format {}", format); 74 throw InvalidArgument("Invalid image format {}", format);
75} 75}
76 76
77spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
78 const auto spv_format = GetImageFormat(format);
79 return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
80}
81
82Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { 77Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
83 const spv::ImageFormat format{GetImageFormat(desc.format)}; 78 const spv::ImageFormat format{GetImageFormat(desc.format)};
84 const Id type{ctx.U32[1]}; 79 const Id type{ctx.U32[1]};
@@ -1275,7 +1270,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
1275 if (desc.count != 1) { 1270 if (desc.count != 1) {
1276 throw NotImplementedException("Array of image buffers"); 1271 throw NotImplementedException("Array of image buffers");
1277 } 1272 }
1278 const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; 1273 const spv::ImageFormat format{GetImageFormat(desc.format)};
1279 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; 1274 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
1280 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; 1275 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
1281 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; 1276 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 9b13ccbab..cf9266d54 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -95,6 +95,12 @@ add_library(video_core STATIC
95 memory_manager.h 95 memory_manager.h
96 precompiled_headers.h 96 precompiled_headers.h
97 pte_kind.h 97 pte_kind.h
98 query_cache/bank_base.h
99 query_cache/query_base.h
100 query_cache/query_cache_base.h
101 query_cache/query_cache.h
102 query_cache/query_stream.h
103 query_cache/types.h
98 query_cache.h 104 query_cache.h
99 rasterizer_accelerated.cpp 105 rasterizer_accelerated.cpp
100 rasterizer_accelerated.h 106 rasterizer_accelerated.h
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 8be7bd594..9e90c587c 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -272,13 +272,19 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
272 if (!cpu_addr) { 272 if (!cpu_addr) {
273 return {&slot_buffers[NULL_BUFFER_ID], 0}; 273 return {&slot_buffers[NULL_BUFFER_ID], 0};
274 } 274 }
275 const BufferId buffer_id = FindBuffer(*cpu_addr, size); 275 return ObtainCPUBuffer(*cpu_addr, size, sync_info, post_op);
276}
277
278template <class P>
279std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
280 VAddr cpu_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
281 const BufferId buffer_id = FindBuffer(cpu_addr, size);
276 Buffer& buffer = slot_buffers[buffer_id]; 282 Buffer& buffer = slot_buffers[buffer_id];
277 283
278 // synchronize op 284 // synchronize op
279 switch (sync_info) { 285 switch (sync_info) {
280 case ObtainBufferSynchronize::FullSynchronize: 286 case ObtainBufferSynchronize::FullSynchronize:
281 SynchronizeBuffer(buffer, *cpu_addr, size); 287 SynchronizeBuffer(buffer, cpu_addr, size);
282 break; 288 break;
283 default: 289 default:
284 break; 290 break;
@@ -286,11 +292,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
286 292
287 switch (post_op) { 293 switch (post_op) {
288 case ObtainBufferOperation::MarkAsWritten: 294 case ObtainBufferOperation::MarkAsWritten:
289 MarkWrittenBuffer(buffer_id, *cpu_addr, size); 295 MarkWrittenBuffer(buffer_id, cpu_addr, size);
290 break; 296 break;
291 case ObtainBufferOperation::DiscardWrite: { 297 case ObtainBufferOperation::DiscardWrite: {
292 VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64); 298 VAddr cpu_addr_start = Common::AlignDown(cpu_addr, 64);
293 VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64); 299 VAddr cpu_addr_end = Common::AlignUp(cpu_addr + size, 64);
294 IntervalType interval{cpu_addr_start, cpu_addr_end}; 300 IntervalType interval{cpu_addr_start, cpu_addr_end};
295 ClearDownload(interval); 301 ClearDownload(interval);
296 common_ranges.subtract(interval); 302 common_ranges.subtract(interval);
@@ -300,7 +306,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
300 break; 306 break;
301 } 307 }
302 308
303 return {&buffer, buffer.Offset(*cpu_addr)}; 309 return {&buffer, buffer.Offset(cpu_addr)};
304} 310}
305 311
306template <class P> 312template <class P>
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 0b7135d49..c4f6e8d12 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -295,6 +295,10 @@ public:
295 [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size, 295 [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
296 ObtainBufferSynchronize sync_info, 296 ObtainBufferSynchronize sync_info,
297 ObtainBufferOperation post_op); 297 ObtainBufferOperation post_op);
298
299 [[nodiscard]] std::pair<Buffer*, u32> ObtainCPUBuffer(VAddr gpu_addr, u32 size,
300 ObtainBufferSynchronize sync_info,
301 ObtainBufferOperation post_op);
298 void FlushCachedWrites(); 302 void FlushCachedWrites();
299 303
300 /// Return true when there are uncommitted buffers to be downloaded 304 /// Return true when there are uncommitted buffers to be downloaded
@@ -335,6 +339,14 @@ public:
335 339
336 [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer(); 340 [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
337 341
342 template <typename Func>
343 void BufferOperations(Func&& func) {
344 do {
345 channel_state->has_deleted_buffers = false;
346 func();
347 } while (channel_state->has_deleted_buffers);
348 }
349
338 std::recursive_mutex mutex; 350 std::recursive_mutex mutex;
339 Runtime& runtime; 351 Runtime& runtime;
340 352
diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h
index 46bc9e322..5574e1fba 100644
--- a/src/video_core/control/channel_state_cache.h
+++ b/src/video_core/control/channel_state_cache.h
@@ -51,7 +51,7 @@ public:
51 virtual void CreateChannel(Tegra::Control::ChannelState& channel); 51 virtual void CreateChannel(Tegra::Control::ChannelState& channel);
52 52
53 /// Bind a channel for execution. 53 /// Bind a channel for execution.
54 void BindToChannel(s32 id); 54 virtual void BindToChannel(s32 id);
55 55
56 /// Erase channel's state. 56 /// Erase channel's state.
57 void EraseChannel(s32 id); 57 void EraseChannel(s32 id);
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 7c22c49f1..18d959143 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -46,6 +46,7 @@ public:
46 }; 46 };
47 47
48 struct IndirectParams { 48 struct IndirectParams {
49 bool is_byte_count;
49 bool is_indexed; 50 bool is_indexed;
50 bool include_count; 51 bool include_count;
51 GPUVAddr count_start_address; 52 GPUVAddr count_start_address;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 06e349e43..32d767d85 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -20,8 +20,6 @@
20 20
21namespace Tegra::Engines { 21namespace Tegra::Engines {
22 22
23using VideoCore::QueryType;
24
25/// First register id that is actually a Macro call. 23/// First register id that is actually a Macro call.
26constexpr u32 MacroRegistersStart = 0xE00; 24constexpr u32 MacroRegistersStart = 0xE00;
27 25
@@ -500,27 +498,21 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
500} 498}
501 499
502void Maxwell3D::ProcessQueryGet() { 500void Maxwell3D::ProcessQueryGet() {
501 VideoCommon::QueryPropertiesFlags flags{};
502 if (regs.report_semaphore.query.short_query == 0) {
503 flags |= VideoCommon::QueryPropertiesFlags::HasTimeout;
504 }
505 const GPUVAddr sequence_address{regs.report_semaphore.Address()};
506 const VideoCommon::QueryType query_type =
507 static_cast<VideoCommon::QueryType>(regs.report_semaphore.query.report.Value());
508 const u32 payload = regs.report_semaphore.payload;
509 const u32 subreport = regs.report_semaphore.query.sub_report;
503 switch (regs.report_semaphore.query.operation) { 510 switch (regs.report_semaphore.query.operation) {
504 case Regs::ReportSemaphore::Operation::Release: 511 case Regs::ReportSemaphore::Operation::Release:
505 if (regs.report_semaphore.query.short_query != 0) { 512 if (regs.report_semaphore.query.short_query != 0) {
506 const GPUVAddr sequence_address{regs.report_semaphore.Address()}; 513 flags |= VideoCommon::QueryPropertiesFlags::IsAFence;
507 const u32 payload = regs.report_semaphore.payload;
508 std::function<void()> operation([this, sequence_address, payload] {
509 memory_manager.Write<u32>(sequence_address, payload);
510 });
511 rasterizer->SignalFence(std::move(operation));
512 } else {
513 struct LongQueryResult {
514 u64_le value;
515 u64_le timestamp;
516 };
517 const GPUVAddr sequence_address{regs.report_semaphore.Address()};
518 const u32 payload = regs.report_semaphore.payload;
519 [this, sequence_address, payload] {
520 memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
521 memory_manager.Write<u64>(sequence_address, payload);
522 }();
523 } 514 }
515 rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
524 break; 516 break;
525 case Regs::ReportSemaphore::Operation::Acquire: 517 case Regs::ReportSemaphore::Operation::Acquire:
526 // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that 518 // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
@@ -528,11 +520,7 @@ void Maxwell3D::ProcessQueryGet() {
528 UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE"); 520 UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE");
529 break; 521 break;
530 case Regs::ReportSemaphore::Operation::ReportOnly: 522 case Regs::ReportSemaphore::Operation::ReportOnly:
531 if (const std::optional<u64> result = GetQueryResult()) { 523 rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
532 // If the query returns an empty optional it means it's cached and deferred.
533 // In this case we have a non-empty result, so we stamp it immediately.
534 StampQueryResult(*result, regs.report_semaphore.query.short_query == 0);
535 }
536 break; 524 break;
537 case Regs::ReportSemaphore::Operation::Trap: 525 case Regs::ReportSemaphore::Operation::Trap:
538 UNIMPLEMENTED_MSG("Unimplemented query operation TRAP"); 526 UNIMPLEMENTED_MSG("Unimplemented query operation TRAP");
@@ -544,6 +532,10 @@ void Maxwell3D::ProcessQueryGet() {
544} 532}
545 533
546void Maxwell3D::ProcessQueryCondition() { 534void Maxwell3D::ProcessQueryCondition() {
535 if (rasterizer->AccelerateConditionalRendering()) {
536 execute_on = true;
537 return;
538 }
547 const GPUVAddr condition_address{regs.render_enable.Address()}; 539 const GPUVAddr condition_address{regs.render_enable.Address()};
548 switch (regs.render_enable_override) { 540 switch (regs.render_enable_override) {
549 case Regs::RenderEnable::Override::AlwaysRender: 541 case Regs::RenderEnable::Override::AlwaysRender:
@@ -553,10 +545,6 @@ void Maxwell3D::ProcessQueryCondition() {
553 execute_on = false; 545 execute_on = false;
554 break; 546 break;
555 case Regs::RenderEnable::Override::UseRenderEnable: { 547 case Regs::RenderEnable::Override::UseRenderEnable: {
556 if (rasterizer->AccelerateConditionalRendering()) {
557 execute_on = true;
558 return;
559 }
560 switch (regs.render_enable.mode) { 548 switch (regs.render_enable.mode) {
561 case Regs::RenderEnable::Mode::True: { 549 case Regs::RenderEnable::Mode::True: {
562 execute_on = true; 550 execute_on = true;
@@ -598,15 +586,9 @@ void Maxwell3D::ProcessQueryCondition() {
598} 586}
599 587
600void Maxwell3D::ProcessCounterReset() { 588void Maxwell3D::ProcessCounterReset() {
601#if ANDROID
602 if (!Settings::IsGPULevelHigh()) {
603 // This is problematic on Android, disable on GPU Normal.
604 return;
605 }
606#endif
607 switch (regs.clear_report_value) { 589 switch (regs.clear_report_value) {
608 case Regs::ClearReport::ZPassPixelCount: 590 case Regs::ClearReport::ZPassPixelCount:
609 rasterizer->ResetCounter(QueryType::SamplesPassed); 591 rasterizer->ResetCounter(VideoCommon::QueryType::ZPassPixelCount64);
610 break; 592 break;
611 default: 593 default:
612 LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value); 594 LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value);
@@ -620,28 +602,6 @@ void Maxwell3D::ProcessSyncPoint() {
620 rasterizer->SignalSyncPoint(sync_point); 602 rasterizer->SignalSyncPoint(sync_point);
621} 603}
622 604
623std::optional<u64> Maxwell3D::GetQueryResult() {
624 switch (regs.report_semaphore.query.report) {
625 case Regs::ReportSemaphore::Report::Payload:
626 return regs.report_semaphore.payload;
627 case Regs::ReportSemaphore::Report::ZPassPixelCount64:
628#if ANDROID
629 if (!Settings::IsGPULevelHigh()) {
630 // This is problematic on Android, disable on GPU Normal.
631 return 120;
632 }
633#endif
634 // Deferred.
635 rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed,
636 system.GPU().GetTicks());
637 return std::nullopt;
638 default:
639 LOG_DEBUG(HW_GPU, "Unimplemented query report type {}",
640 regs.report_semaphore.query.report.Value());
641 return 1;
642 }
643}
644
645void Maxwell3D::ProcessCBBind(size_t stage_index) { 605void Maxwell3D::ProcessCBBind(size_t stage_index) {
646 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader 606 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader
647 // stage. 607 // stage.
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 6c19354e1..17faacc37 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -3182,9 +3182,6 @@ private:
3182 /// Handles writes to syncing register. 3182 /// Handles writes to syncing register.
3183 void ProcessSyncPoint(); 3183 void ProcessSyncPoint();
3184 3184
3185 /// Returns a query's value or an empty object if the value will be deferred through a cache.
3186 std::optional<u64> GetQueryResult();
3187
3188 void RefreshParametersImpl(); 3185 void RefreshParametersImpl();
3189 3186
3190 bool IsMethodExecutable(u32 method); 3187 bool IsMethodExecutable(u32 method);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index da8eab7ee..422d4d859 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -109,10 +109,11 @@ void MaxwellDMA::Launch() {
109 const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; 109 const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
110 if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { 110 if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
111 ASSERT(regs.remap_const.component_size_minus_one == 3); 111 ASSERT(regs.remap_const.component_size_minus_one == 3);
112 accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); 112 accelerate.BufferClear(regs.offset_out, regs.line_length_in,
113 regs.remap_const.remap_consta_value);
113 read_buffer.resize_destructive(regs.line_length_in * sizeof(u32)); 114 read_buffer.resize_destructive(regs.line_length_in * sizeof(u32));
114 std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in); 115 std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in);
115 std::ranges::fill(span, regs.remap_consta_value); 116 std::ranges::fill(span, regs.remap_const.remap_consta_value);
116 memory_manager.WriteBlockUnsafe(regs.offset_out, 117 memory_manager.WriteBlockUnsafe(regs.offset_out,
117 reinterpret_cast<u8*>(read_buffer.data()), 118 reinterpret_cast<u8*>(read_buffer.data()),
118 regs.line_length_in * sizeof(u32)); 119 regs.line_length_in * sizeof(u32));
@@ -361,21 +362,17 @@ void MaxwellDMA::ReleaseSemaphore() {
361 const auto type = regs.launch_dma.semaphore_type; 362 const auto type = regs.launch_dma.semaphore_type;
362 const GPUVAddr address = regs.semaphore.address; 363 const GPUVAddr address = regs.semaphore.address;
363 const u32 payload = regs.semaphore.payload; 364 const u32 payload = regs.semaphore.payload;
365 VideoCommon::QueryPropertiesFlags flags{VideoCommon::QueryPropertiesFlags::IsAFence};
364 switch (type) { 366 switch (type) {
365 case LaunchDMA::SemaphoreType::NONE: 367 case LaunchDMA::SemaphoreType::NONE:
366 break; 368 break;
367 case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: { 369 case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
368 std::function<void()> operation( 370 rasterizer->Query(address, VideoCommon::QueryType::Payload, flags, payload, 0);
369 [this, address, payload] { memory_manager.Write<u32>(address, payload); });
370 rasterizer->SignalFence(std::move(operation));
371 break; 371 break;
372 } 372 }
373 case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: { 373 case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
374 std::function<void()> operation([this, address, payload] { 374 rasterizer->Query(address, VideoCommon::QueryType::Payload,
375 memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks()); 375 flags | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
376 memory_manager.Write<u64>(address, payload);
377 });
378 rasterizer->SignalFence(std::move(operation));
379 break; 376 break;
380 } 377 }
381 default: 378 default:
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 69e26cb32..1a43e24b6 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -214,14 +214,15 @@ public:
214 NO_WRITE = 6, 214 NO_WRITE = 6,
215 }; 215 };
216 216
217 PackedGPUVAddr address; 217 u32 remap_consta_value;
218 u32 remap_constb_value;
218 219
219 union { 220 union {
221 BitField<0, 12, u32> dst_components_raw;
220 BitField<0, 3, Swizzle> dst_x; 222 BitField<0, 3, Swizzle> dst_x;
221 BitField<4, 3, Swizzle> dst_y; 223 BitField<4, 3, Swizzle> dst_y;
222 BitField<8, 3, Swizzle> dst_z; 224 BitField<8, 3, Swizzle> dst_z;
223 BitField<12, 3, Swizzle> dst_w; 225 BitField<12, 3, Swizzle> dst_w;
224 BitField<0, 12, u32> dst_components_raw;
225 BitField<16, 2, u32> component_size_minus_one; 226 BitField<16, 2, u32> component_size_minus_one;
226 BitField<20, 2, u32> num_src_components_minus_one; 227 BitField<20, 2, u32> num_src_components_minus_one;
227 BitField<24, 2, u32> num_dst_components_minus_one; 228 BitField<24, 2, u32> num_dst_components_minus_one;
@@ -274,55 +275,57 @@ private:
274 struct Regs { 275 struct Regs {
275 union { 276 union {
276 struct { 277 struct {
277 u32 reserved[0x40]; 278 INSERT_PADDING_BYTES_NOINIT(0x100);
278 u32 nop; 279 u32 nop;
279 u32 reserved01[0xf]; 280 INSERT_PADDING_BYTES_NOINIT(0x3C);
280 u32 pm_trigger; 281 u32 pm_trigger;
281 u32 reserved02[0x3f]; 282 INSERT_PADDING_BYTES_NOINIT(0xFC);
282 Semaphore semaphore; 283 Semaphore semaphore;
283 u32 reserved03[0x2]; 284 INSERT_PADDING_BYTES_NOINIT(0x8);
284 RenderEnable render_enable; 285 RenderEnable render_enable;
285 PhysMode src_phys_mode; 286 PhysMode src_phys_mode;
286 PhysMode dst_phys_mode; 287 PhysMode dst_phys_mode;
287 u32 reserved04[0x26]; 288 INSERT_PADDING_BYTES_NOINIT(0x98);
288 LaunchDMA launch_dma; 289 LaunchDMA launch_dma;
289 u32 reserved05[0x3f]; 290 INSERT_PADDING_BYTES_NOINIT(0xFC);
290 PackedGPUVAddr offset_in; 291 PackedGPUVAddr offset_in;
291 PackedGPUVAddr offset_out; 292 PackedGPUVAddr offset_out;
292 s32 pitch_in; 293 s32 pitch_in;
293 s32 pitch_out; 294 s32 pitch_out;
294 u32 line_length_in; 295 u32 line_length_in;
295 u32 line_count; 296 u32 line_count;
296 u32 reserved06[0xb6]; 297 INSERT_PADDING_BYTES_NOINIT(0x2E0);
297 u32 remap_consta_value;
298 u32 remap_constb_value;
299 RemapConst remap_const; 298 RemapConst remap_const;
300 DMA::Parameters dst_params; 299 DMA::Parameters dst_params;
301 u32 reserved07[0x1]; 300 INSERT_PADDING_BYTES_NOINIT(0x4);
302 DMA::Parameters src_params; 301 DMA::Parameters src_params;
303 u32 reserved08[0x275]; 302 INSERT_PADDING_BYTES_NOINIT(0x9D4);
304 u32 pm_trigger_end; 303 u32 pm_trigger_end;
305 u32 reserved09[0x3ba]; 304 INSERT_PADDING_BYTES_NOINIT(0xEE8);
306 }; 305 };
307 std::array<u32, NUM_REGS> reg_array; 306 std::array<u32, NUM_REGS> reg_array;
308 }; 307 };
309 } regs{}; 308 } regs{};
309 static_assert(sizeof(Regs) == NUM_REGS * 4);
310 310
311#define ASSERT_REG_POSITION(field_name, position) \ 311#define ASSERT_REG_POSITION(field_name, position) \
312 static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \ 312 static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \
313 "Field " #field_name " has invalid position") 313 "Field " #field_name " has invalid position")
314 314
315 ASSERT_REG_POSITION(launch_dma, 0xC0); 315 ASSERT_REG_POSITION(semaphore, 0x240);
316 ASSERT_REG_POSITION(offset_in, 0x100); 316 ASSERT_REG_POSITION(render_enable, 0x254);
317 ASSERT_REG_POSITION(offset_out, 0x102); 317 ASSERT_REG_POSITION(src_phys_mode, 0x260);
318 ASSERT_REG_POSITION(pitch_in, 0x104); 318 ASSERT_REG_POSITION(launch_dma, 0x300);
319 ASSERT_REG_POSITION(pitch_out, 0x105); 319 ASSERT_REG_POSITION(offset_in, 0x400);
320 ASSERT_REG_POSITION(line_length_in, 0x106); 320 ASSERT_REG_POSITION(offset_out, 0x408);
321 ASSERT_REG_POSITION(line_count, 0x107); 321 ASSERT_REG_POSITION(pitch_in, 0x410);
322 ASSERT_REG_POSITION(remap_const, 0x1C0); 322 ASSERT_REG_POSITION(pitch_out, 0x414);
323 ASSERT_REG_POSITION(dst_params, 0x1C3); 323 ASSERT_REG_POSITION(line_length_in, 0x418);
324 ASSERT_REG_POSITION(src_params, 0x1CA); 324 ASSERT_REG_POSITION(line_count, 0x41C);
325 325 ASSERT_REG_POSITION(remap_const, 0x700);
326 ASSERT_REG_POSITION(dst_params, 0x70C);
327 ASSERT_REG_POSITION(src_params, 0x728);
328 ASSERT_REG_POSITION(pm_trigger_end, 0x1114);
326#undef ASSERT_REG_POSITION 329#undef ASSERT_REG_POSITION
327}; 330};
328 331
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 6de2543b7..8dd34c04a 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -82,10 +82,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
82 if (op == GpuSemaphoreOperation::WriteLong) { 82 if (op == GpuSemaphoreOperation::WriteLong) {
83 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; 83 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
84 const u32 payload = regs.semaphore_sequence; 84 const u32 payload = regs.semaphore_sequence;
85 [this, sequence_address, payload] { 85 rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
86 memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks()); 86 VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
87 memory_manager.Write<u64>(sequence_address, payload);
88 }();
89 } else { 87 } else {
90 do { 88 do {
91 const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())}; 89 const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
@@ -120,10 +118,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
120void Puller::ProcessSemaphoreRelease() { 118void Puller::ProcessSemaphoreRelease() {
121 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; 119 const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
122 const u32 payload = regs.semaphore_release; 120 const u32 payload = regs.semaphore_release;
123 std::function<void()> operation([this, sequence_address, payload] { 121 rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
124 memory_manager.Write<u32>(sequence_address, payload); 122 VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
125 });
126 rasterizer->SignalFence(std::move(operation));
127} 123}
128 124
129void Puller::ProcessSemaphoreAcquire() { 125void Puller::ProcessSemaphoreAcquire() {
@@ -132,7 +128,6 @@ void Puller::ProcessSemaphoreAcquire() {
132 while (word != value) { 128 while (word != value) {
133 regs.acquire_active = true; 129 regs.acquire_active = true;
134 regs.acquire_value = value; 130 regs.acquire_value = value;
135 std::this_thread::sleep_for(std::chrono::milliseconds(1));
136 rasterizer->ReleaseFences(); 131 rasterizer->ReleaseFences();
137 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()); 132 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
138 // TODO(kemathe73) figure out how to do the acquire_timeout 133 // TODO(kemathe73) figure out how to do the acquire_timeout
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index ab20ff30f..805a89900 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -55,6 +55,9 @@ public:
55 55
56 // Unlike other fences, this one doesn't 56 // Unlike other fences, this one doesn't
57 void SignalOrdering() { 57 void SignalOrdering() {
58 if constexpr (!can_async_check) {
59 TryReleasePendingFences<false>();
60 }
58 std::scoped_lock lock{buffer_cache.mutex}; 61 std::scoped_lock lock{buffer_cache.mutex};
59 buffer_cache.AccumulateFlushes(); 62 buffer_cache.AccumulateFlushes();
60 } 63 }
@@ -104,9 +107,25 @@ public:
104 SignalFence(std::move(func)); 107 SignalFence(std::move(func));
105 } 108 }
106 109
107 void WaitPendingFences() { 110 void WaitPendingFences([[maybe_unused]] bool force) {
108 if constexpr (!can_async_check) { 111 if constexpr (!can_async_check) {
109 TryReleasePendingFences<true>(); 112 TryReleasePendingFences<true>();
113 } else {
114 if (!force) {
115 return;
116 }
117 std::mutex wait_mutex;
118 std::condition_variable wait_cv;
119 std::atomic<bool> wait_finished{};
120 std::function<void()> func([&] {
121 std::scoped_lock lk(wait_mutex);
122 wait_finished.store(true, std::memory_order_relaxed);
123 wait_cv.notify_all();
124 });
125 SignalFence(std::move(func));
126 std::unique_lock lk(wait_mutex);
127 wait_cv.wait(
128 lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); });
110 } 129 }
111 } 130 }
112 131
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index c192e33b2..11549d448 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -102,7 +102,8 @@ struct GPU::Impl {
102 102
103 /// Signal the ending of command list. 103 /// Signal the ending of command list.
104 void OnCommandListEnd() { 104 void OnCommandListEnd() {
105 rasterizer->ReleaseFences(); 105 rasterizer->ReleaseFences(false);
106 Settings::UpdateGPUAccuracy();
106 } 107 }
107 108
108 /// Request a host GPU memory flush from the CPU. 109 /// Request a host GPU memory flush from the CPU.
@@ -220,6 +221,7 @@ struct GPU::Impl {
220 /// This can be used to launch any necessary threads and register any necessary 221 /// This can be used to launch any necessary threads and register any necessary
221 /// core timing events. 222 /// core timing events.
222 void Start() { 223 void Start() {
224 Settings::UpdateGPUAccuracy();
223 gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); 225 gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
224 } 226 }
225 227
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index c4d459077..6b912027f 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -41,6 +41,9 @@ set(SHADER_FILES
41 pitch_unswizzle.comp 41 pitch_unswizzle.comp
42 present_bicubic.frag 42 present_bicubic.frag
43 present_gaussian.frag 43 present_gaussian.frag
44 queries_prefix_scan_sum.comp
45 queries_prefix_scan_sum_nosubgroups.comp
46 resolve_conditional_render.comp
44 smaa_edge_detection.vert 47 smaa_edge_detection.vert
45 smaa_edge_detection.frag 48 smaa_edge_detection.frag
46 smaa_blending_weight_calculation.vert 49 smaa_blending_weight_calculation.vert
@@ -70,6 +73,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
70endif() 73endif()
71 74
72set(GLSL_FLAGS "") 75set(GLSL_FLAGS "")
76set(SPIR_V_VERSION "spirv1.3")
73set(QUIET_FLAG "--quiet") 77set(QUIET_FLAG "--quiet")
74 78
75set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) 79set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
@@ -123,7 +127,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
123 OUTPUT 127 OUTPUT
124 ${SPIRV_HEADER_FILE} 128 ${SPIRV_HEADER_FILE}
125 COMMAND 129 COMMAND
126 ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} 130 ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} --target-env ${SPIR_V_VERSION}
127 MAIN_DEPENDENCY 131 MAIN_DEPENDENCY
128 ${SOURCE_FILE} 132 ${SOURCE_FILE}
129 ) 133 )
diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
index fc3854d18..66f2ad483 100644
--- a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
+++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
@@ -15,11 +15,14 @@ void main() {
15 15
16 // TODO: Specialization constants for num_samples? 16 // TODO: Specialization constants for num_samples?
17 const int num_samples = imageSamples(msaa_in); 17 const int num_samples = imageSamples(msaa_in);
18 const ivec3 msaa_size = imageSize(msaa_in);
19 const ivec3 out_size = imageSize(output_img);
20 const ivec3 scale = out_size / msaa_size;
18 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { 21 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
19 const vec4 pixel = imageLoad(msaa_in, coords, curr_sample); 22 const vec4 pixel = imageLoad(msaa_in, coords, curr_sample);
20 23
21 const int single_sample_x = 2 * coords.x + (curr_sample & 1); 24 const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
22 const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); 25 const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
23 const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z); 26 const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z);
24 27
25 if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) { 28 if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) {
diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
index dedd962f1..c7ce38efa 100644
--- a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
+++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
@@ -15,9 +15,12 @@ void main() {
15 15
16 // TODO: Specialization constants for num_samples? 16 // TODO: Specialization constants for num_samples?
17 const int num_samples = imageSamples(output_msaa); 17 const int num_samples = imageSamples(output_msaa);
18 const ivec3 msaa_size = imageSize(output_msaa);
19 const ivec3 out_size = imageSize(img_in);
20 const ivec3 scale = out_size / msaa_size;
18 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { 21 for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
19 const int single_sample_x = 2 * coords.x + (curr_sample & 1); 22 const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
20 const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1); 23 const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
21 const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z); 24 const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z);
22 25
23 if (any(greaterThanEqual(single_coords, imageSize(img_in)))) { 26 if (any(greaterThanEqual(single_coords, imageSize(img_in)))) {
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum.comp b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
new file mode 100644
index 000000000..6faa8981f
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#version 460 core
5
6#extension GL_KHR_shader_subgroup_basic : require
7#extension GL_KHR_shader_subgroup_shuffle : require
8#extension GL_KHR_shader_subgroup_shuffle_relative : require
9#extension GL_KHR_shader_subgroup_arithmetic : require
10
11#ifdef VULKAN
12
13#define HAS_EXTENDED_TYPES 1
14#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
15#define END_PUSH_CONSTANTS };
16#define UNIFORM(n)
17#define BINDING_INPUT_BUFFER 0
18#define BINDING_OUTPUT_IMAGE 1
19
20#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
21
22#extension GL_NV_gpu_shader5 : enable
23#ifdef GL_NV_gpu_shader5
24#define HAS_EXTENDED_TYPES 1
25#else
26#define HAS_EXTENDED_TYPES 0
27#endif
28#define BEGIN_PUSH_CONSTANTS
29#define END_PUSH_CONSTANTS
30#define UNIFORM(n) layout(location = n) uniform
31#define BINDING_INPUT_BUFFER 0
32#define BINDING_OUTPUT_IMAGE 0
33
34#endif
35
36BEGIN_PUSH_CONSTANTS
37UNIFORM(0) uint min_accumulation_base;
38UNIFORM(1) uint max_accumulation_base;
39UNIFORM(2) uint accumulation_limit;
40UNIFORM(3) uint buffer_offset;
41END_PUSH_CONSTANTS
42
43#define LOCAL_RESULTS 8
44#define QUERIES_PER_INVOC 2048
45
46layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
47
48layout(std430, binding = 0) readonly buffer block1 {
49 uvec2 input_data[];
50};
51
52layout(std430, binding = 1) coherent buffer block2 {
53 uvec2 output_data[];
54};
55
56layout(std430, binding = 2) coherent buffer block3 {
57 uvec2 accumulated_data;
58};
59
60shared uvec2 shared_data[128];
61
62// Simple Uint64 add that uses 2 uint variables for GPUs that don't support uint64
63uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
64 uint carry = 0;
65 uvec2 result;
66 result.x = uaddCarry(value_1.x, value_2.x, carry);
67 result.y = value_1.y + value_2.y + carry;
68 return result;
69}
70
71// do subgroup Prefix Sum using Hillis and Steele's algorithm
72uvec2 subgroupInclusiveAddUint64(uvec2 value) {
73 uvec2 result = value;
74 for (uint i = 1; i < gl_SubgroupSize; i *= 2) {
75 uvec2 other = subgroupShuffleUp(result, i); // get value from subgroup_inv_id - i;
76 if (i <= gl_SubgroupInvocationID) {
77 result = AddUint64(result, other);
78 }
79 }
80 return result;
81}
82
83// Writes down the results to the output buffer and to the accumulation buffer
84void WriteResults(uvec2 results[LOCAL_RESULTS]) {
85 const uint current_id = gl_LocalInvocationID.x;
86 const uvec2 accum = accumulated_data;
87 for (uint i = 0; i < LOCAL_RESULTS; i++) {
88 uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0);
89 AddUint64(results[i], base_data);
90 }
91 for (uint i = 0; i < LOCAL_RESULTS; i++) {
92 output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i];
93 }
94 uint index = accumulation_limit % LOCAL_RESULTS;
95 uint base_id = accumulation_limit / LOCAL_RESULTS;
96 if (min_accumulation_base >= accumulation_limit + 1) {
97 if (current_id == base_id) {
98 accumulated_data = results[index];
99 }
100 return;
101 }
102 // We have that ugly case in which the accumulation data is reset in the middle somewhere.
103 barrier();
104 groupMemoryBarrier();
105
106 if (current_id == base_id) {
107 uvec2 reset_value = output_data[max_accumulation_base - 1];
108 // Calculate two complement / negate manually
109 reset_value = AddUint64(uvec2(1,0), ~reset_value);
110 accumulated_data = AddUint64(results[index], reset_value);
111 }
112}
113
114void main() {
115 const uint subgroup_inv_id = gl_SubgroupInvocationID;
116 const uint subgroup_id = gl_SubgroupID + gl_WorkGroupID.x * gl_NumSubgroups;
117 const uint last_subgroup_id = subgroupMax(subgroup_inv_id);
118 const uint current_id = gl_LocalInvocationID.x;
119 const uint total_work = accumulation_limit;
120 const uint last_result_id = LOCAL_RESULTS - 1;
121 uvec2 data[LOCAL_RESULTS];
122 for (uint i = 0; i < LOCAL_RESULTS; i++) {
123 data[i] = input_data[buffer_offset + current_id * LOCAL_RESULTS + i];
124 }
125 uvec2 results[LOCAL_RESULTS];
126 results[0] = data[0];
127 for (uint i = 1; i < LOCAL_RESULTS; i++) {
128 results[i] = AddUint64(data[i], results[i - 1]);
129 }
130 // make sure all input data has been loaded
131 subgroupBarrier();
132 subgroupMemoryBarrier();
133
134 // on the last local result, do a subgroup inclusive scan sum
135 results[last_result_id] = subgroupInclusiveAddUint64(results[last_result_id]);
136 // get the last local result from the subgroup behind the current
137 uvec2 result_behind = subgroupShuffleUp(results[last_result_id], 1);
138 if (subgroup_inv_id != 0) {
139 for (uint i = 1; i < LOCAL_RESULTS; i++) {
140 results[i - 1] = AddUint64(results[i - 1], result_behind);
141 }
142 }
143
144 // if we had less queries than our subgroup, just write down the results.
145 if (total_work <= gl_SubgroupSize * LOCAL_RESULTS) { // This condition is constant per dispatch.
146 WriteResults(results);
147 return;
148 }
149
150 // We now have more, so lets write the last result into shared memory.
151 // Only pick the last subgroup.
152 if (subgroup_inv_id == last_subgroup_id) {
153 shared_data[subgroup_id] = results[last_result_id];
154 }
155 // wait until everyone loaded their stuffs
156 barrier();
157 memoryBarrierShared();
158
159 // only if it's not the first subgroup
160 if (subgroup_id != 0) {
161 // get the results from some previous invocation
162 uvec2 tmp = shared_data[subgroup_inv_id];
163 subgroupBarrier();
164 subgroupMemoryBarrierShared();
165 tmp = subgroupInclusiveAddUint64(tmp);
166 // obtain the result that would be equivalent to the previous result
167 uvec2 shuffled_result = subgroupShuffle(tmp, subgroup_id - 1);
168 for (uint i = 0; i < LOCAL_RESULTS; i++) {
169 results[i] = AddUint64(results[i], shuffled_result);
170 }
171 }
172 WriteResults(results);
173} \ No newline at end of file
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
new file mode 100644
index 000000000..559a213b9
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: Copyright 2015 Graham Sellers, Richard Wright Jr. and Nicholas Haemel
2// SPDX-License-Identifier: MIT
3
4// Code obtained from OpenGL SuperBible, Seventh Edition by Graham Sellers, Richard Wright Jr. and
5// Nicholas Haemel. Modified to suit needs.
6
7#version 460 core
8
9#ifdef VULKAN
10
11#define HAS_EXTENDED_TYPES 1
12#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
13#define END_PUSH_CONSTANTS };
14#define UNIFORM(n)
15#define BINDING_INPUT_BUFFER 0
16#define BINDING_OUTPUT_IMAGE 1
17
18#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
19
20#extension GL_NV_gpu_shader5 : enable
21#ifdef GL_NV_gpu_shader5
22#define HAS_EXTENDED_TYPES 1
23#else
24#define HAS_EXTENDED_TYPES 0
25#endif
26#define BEGIN_PUSH_CONSTANTS
27#define END_PUSH_CONSTANTS
28#define UNIFORM(n) layout(location = n) uniform
29#define BINDING_INPUT_BUFFER 0
30#define BINDING_OUTPUT_IMAGE 0
31
32#endif
33
34BEGIN_PUSH_CONSTANTS
35UNIFORM(0) uint min_accumulation_base;
36UNIFORM(1) uint max_accumulation_base;
37UNIFORM(2) uint accumulation_limit;
38UNIFORM(3) uint buffer_offset;
39END_PUSH_CONSTANTS
40
41#define LOCAL_RESULTS 4
42#define QUERIES_PER_INVOC 2048
43
44layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
45
46layout(std430, binding = 0) readonly buffer block1 {
47 uvec2 input_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
48};
49
50layout(std430, binding = 1) writeonly coherent buffer block2 {
51 uvec2 output_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
52};
53
54layout(std430, binding = 2) coherent buffer block3 {
55 uvec2 accumulated_data;
56};
57
58shared uvec2 shared_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
59
60uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
61 uint carry = 0;
62 uvec2 result;
63 result.x = uaddCarry(value_1.x, value_2.x, carry);
64 result.y = value_1.y + value_2.y + carry;
65 return result;
66}
67
68void main(void) {
69 uint id = gl_LocalInvocationID.x;
70 uvec2 base_value[LOCAL_RESULTS];
71 const uvec2 accum = accumulated_data;
72 for (uint i = 0; i < LOCAL_RESULTS; i++) {
73 base_value[i] = (buffer_offset + id * LOCAL_RESULTS + i) < min_accumulation_base
74 ? accumulated_data
75 : uvec2(0);
76 }
77 uint work_size = gl_WorkGroupSize.x;
78 uint rd_id;
79 uint wr_id;
80 uint mask;
81 uvec2 inputs[LOCAL_RESULTS];
82 for (uint i = 0; i < LOCAL_RESULTS; i++) {
83 inputs[i] = input_data[buffer_offset + id * LOCAL_RESULTS + i];
84 }
85 // The number of steps is the log base 2 of the
86 // work group size, which should be a power of 2
87 const uint steps = uint(log2(work_size)) + uint(log2(LOCAL_RESULTS));
88 uint step = 0;
89
90 // Each invocation is responsible for the content of
91 // two elements of the output array
92 for (uint i = 0; i < LOCAL_RESULTS; i++) {
93 shared_data[id * LOCAL_RESULTS + i] = inputs[i];
94 }
95 // Synchronize to make sure that everyone has initialized
96 // their elements of shared_data[] with data loaded from
97 // the input arrays
98 barrier();
99 memoryBarrierShared();
100 // For each step...
101 for (step = 0; step < steps; step++) {
102 // Calculate the read and write index in the
103 // shared array
104 mask = (1 << step) - 1;
105 rd_id = ((id >> step) << (step + 1)) + mask;
106 wr_id = rd_id + 1 + (id & mask);
107 // Accumulate the read data into our element
108
109 shared_data[wr_id] = AddUint64(shared_data[rd_id], shared_data[wr_id]);
110 // Synchronize again to make sure that everyone
111 // has caught up with us
112 barrier();
113 memoryBarrierShared();
114 }
115 // Add the accumulation
116 for (uint i = 0; i < LOCAL_RESULTS; i++) {
117 shared_data[id * LOCAL_RESULTS + i] =
118 AddUint64(shared_data[id * LOCAL_RESULTS + i], base_value[i]);
119 }
120 barrier();
121 memoryBarrierShared();
122
123 // Finally write our data back to the output buffer
124 for (uint i = 0; i < LOCAL_RESULTS; i++) {
125 output_data[buffer_offset + id * LOCAL_RESULTS + i] = shared_data[id * LOCAL_RESULTS + i];
126 }
127 if (id == 0) {
128 if (min_accumulation_base >= accumulation_limit + 1) {
129 accumulated_data = shared_data[accumulation_limit];
130 return;
131 }
132 uvec2 reset_value = shared_data[max_accumulation_base - 1];
133 uvec2 final_value = shared_data[accumulation_limit];
134 // Two complements
135 reset_value = AddUint64(uvec2(1, 0), ~reset_value);
136 accumulated_data = AddUint64(final_value, reset_value);
137 }
138} \ No newline at end of file
diff --git a/src/video_core/host_shaders/resolve_conditional_render.comp b/src/video_core/host_shaders/resolve_conditional_render.comp
new file mode 100644
index 000000000..307e77d1a
--- /dev/null
+++ b/src/video_core/host_shaders/resolve_conditional_render.comp
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#version 450
5
6layout(local_size_x = 1) in;
7
8layout(std430, binding = 0) buffer Query {
9 uvec2 initial;
10 uvec2 unknown;
11 uvec2 current;
12};
13
14layout(std430, binding = 1) buffer Result {
15 uint result;
16};
17
18void main() {
19 result = all(equal(initial, current)) ? 1 : 0;
20}
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 6272a4652..046c8085e 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -67,6 +67,7 @@ public:
67 } 67 }
68 68
69 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 69 auto& params = maxwell3d.draw_manager->GetIndirectParams();
70 params.is_byte_count = false;
70 params.is_indexed = false; 71 params.is_indexed = false;
71 params.include_count = false; 72 params.include_count = false;
72 params.count_start_address = 0; 73 params.count_start_address = 0;
@@ -161,6 +162,7 @@ public:
161 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); 162 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
162 } 163 }
163 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 164 auto& params = maxwell3d.draw_manager->GetIndirectParams();
165 params.is_byte_count = false;
164 params.is_indexed = true; 166 params.is_indexed = true;
165 params.include_count = false; 167 params.include_count = false;
166 params.count_start_address = 0; 168 params.count_start_address = 0;
@@ -256,6 +258,7 @@ public:
256 const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize()); 258 const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize());
257 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; 259 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
258 auto& params = maxwell3d.draw_manager->GetIndirectParams(); 260 auto& params = maxwell3d.draw_manager->GetIndirectParams();
261 params.is_byte_count = false;
259 params.is_indexed = true; 262 params.is_indexed = true;
260 params.include_count = true; 263 params.include_count = true;
261 params.count_start_address = maxwell3d.GetMacroAddress(4); 264 params.count_start_address = maxwell3d.GetMacroAddress(4);
@@ -319,6 +322,47 @@ private:
319 } 322 }
320}; 323};
321 324
325class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
326public:
327 explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
328
329 void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
330 auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
331 if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
332 Fallback(parameters);
333 return;
334 }
335
336 auto& params = maxwell3d.draw_manager->GetIndirectParams();
337 params.is_byte_count = true;
338 params.is_indexed = false;
339 params.include_count = false;
340 params.count_start_address = 0;
341 params.indirect_start_address = maxwell3d.GetMacroAddress(2);
342 params.buffer_size = 4;
343 params.max_draw_counts = 1;
344 params.stride = parameters[1];
345 maxwell3d.regs.draw.begin = parameters[0];
346 maxwell3d.regs.draw_auto_stride = parameters[1];
347 maxwell3d.regs.draw_auto_byte_count = parameters[2];
348
349 maxwell3d.draw_manager->DrawArrayIndirect(topology);
350 }
351
352private:
353 void Fallback(const std::vector<u32>& parameters) {
354 maxwell3d.RefreshParameters();
355
356 maxwell3d.regs.draw.begin = parameters[0];
357 maxwell3d.regs.draw_auto_stride = parameters[1];
358 maxwell3d.regs.draw_auto_byte_count = parameters[2];
359
360 maxwell3d.draw_manager->DrawArray(
361 maxwell3d.regs.draw.topology, 0,
362 maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
363 }
364};
365
322class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { 366class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
323public: 367public:
324 explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} 368 explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
@@ -536,6 +580,11 @@ HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {
536 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> { 580 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
537 return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__); 581 return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__);
538 })); 582 }));
583 builders.emplace(0xB5F74EDB717278ECULL,
584 std::function<std::unique_ptr<CachedMacro>(Maxwell3D&)>(
585 [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
586 return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d__);
587 }));
539} 588}
540 589
541HLEMacro::~HLEMacro() = default; 590HLEMacro::~HLEMacro() = default;
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 7047e2e63..9fcaeeac7 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -25,6 +25,13 @@
25#include "video_core/rasterizer_interface.h" 25#include "video_core/rasterizer_interface.h"
26#include "video_core/texture_cache/slot_vector.h" 26#include "video_core/texture_cache/slot_vector.h"
27 27
28namespace VideoCore {
29enum class QueryType {
30 SamplesPassed,
31};
32constexpr std::size_t NumQueryTypes = 1;
33} // namespace VideoCore
34
28namespace VideoCommon { 35namespace VideoCommon {
29 36
30using AsyncJobId = SlotId; 37using AsyncJobId = SlotId;
@@ -98,10 +105,10 @@ private:
98}; 105};
99 106
100template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> 107template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
101class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> { 108class QueryCacheLegacy : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
102public: 109public:
103 explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_, 110 explicit QueryCacheLegacy(VideoCore::RasterizerInterface& rasterizer_,
104 Core::Memory::Memory& cpu_memory_) 111 Core::Memory::Memory& cpu_memory_)
105 : rasterizer{rasterizer_}, 112 : rasterizer{rasterizer_},
106 // Use reinterpret_cast instead of static_cast as workaround for 113 // Use reinterpret_cast instead of static_cast as workaround for
107 // UBSan bug (https://github.com/llvm/llvm-project/issues/59060) 114 // UBSan bug (https://github.com/llvm/llvm-project/issues/59060)
diff --git a/src/video_core/query_cache/bank_base.h b/src/video_core/query_cache/bank_base.h
new file mode 100644
index 000000000..420927091
--- /dev/null
+++ b/src/video_core/query_cache/bank_base.h
@@ -0,0 +1,104 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <deque>
8#include <utility>
9
10#include "common/common_types.h"
11
12namespace VideoCommon {
13
14class BankBase {
15protected:
16 const size_t base_bank_size{};
17 size_t bank_size{};
18 std::atomic<size_t> references{};
19 size_t current_slot{};
20
21public:
22 explicit BankBase(size_t bank_size_) : base_bank_size{bank_size_}, bank_size(bank_size_) {}
23
24 virtual ~BankBase() = default;
25
26 virtual std::pair<bool, size_t> Reserve() {
27 if (IsClosed()) {
28 return {false, bank_size};
29 }
30 const size_t result = current_slot++;
31 return {true, result};
32 }
33
34 virtual void Reset() {
35 current_slot = 0;
36 references = 0;
37 bank_size = base_bank_size;
38 }
39
40 size_t Size() const {
41 return bank_size;
42 }
43
44 void AddReference(size_t how_many = 1) {
45 references.fetch_add(how_many, std::memory_order_relaxed);
46 }
47
48 void CloseReference(size_t how_many = 1) {
49 if (how_many > references.load(std::memory_order_relaxed)) {
50 UNREACHABLE();
51 }
52 references.fetch_sub(how_many, std::memory_order_relaxed);
53 }
54
55 void Close() {
56 bank_size = current_slot;
57 }
58
59 bool IsClosed() const {
60 return current_slot >= bank_size;
61 }
62
63 bool IsDead() const {
64 return IsClosed() && references == 0;
65 }
66};
67
68template <typename BankType>
69class BankPool {
70private:
71 std::deque<BankType> bank_pool;
72 std::deque<size_t> bank_indices;
73
74public:
75 BankPool() = default;
76 ~BankPool() = default;
77
78 // Reserve a bank from the pool and return its index
79 template <typename Func>
80 size_t ReserveBank(Func&& builder) {
81 if (!bank_indices.empty() && bank_pool[bank_indices.front()].IsDead()) {
82 size_t new_index = bank_indices.front();
83 bank_indices.pop_front();
84 bank_pool[new_index].Reset();
85 return new_index;
86 }
87 size_t new_index = bank_pool.size();
88 builder(bank_pool, new_index);
89 bank_indices.push_back(new_index);
90 return new_index;
91 }
92
93 // Get a reference to a bank using its index
94 BankType& GetBank(size_t index) {
95 return bank_pool[index];
96 }
97
98 // Get the total number of banks in the pool
99 size_t BankCount() const {
100 return bank_pool.size();
101 }
102};
103
104} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_base.h b/src/video_core/query_cache/query_base.h
new file mode 100644
index 000000000..1d786b3a7
--- /dev/null
+++ b/src/video_core/query_cache/query_base.h
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11enum class QueryFlagBits : u32 {
12 HasTimestamp = 1 << 0, ///< Indicates if this query has a timestamp.
13 IsFinalValueSynced = 1 << 1, ///< Indicates if the query has been synced in the host
14 IsHostSynced = 1 << 2, ///< Indicates if the query has been synced in the host
15 IsGuestSynced = 1 << 3, ///< Indicates if the query has been synced with the guest.
16 IsHostManaged = 1 << 4, ///< Indicates if this query points to a host query
17 IsRewritten = 1 << 5, ///< Indicates if this query was rewritten by another query
18 IsInvalidated = 1 << 6, ///< Indicates the value of th query has been nullified.
19 IsOrphan = 1 << 7, ///< Indicates the query has not been set by a guest query.
20 IsFence = 1 << 8, ///< Indicates the query is a fence.
21};
22DECLARE_ENUM_FLAG_OPERATORS(QueryFlagBits)
23
24class QueryBase {
25public:
26 VAddr guest_address{};
27 QueryFlagBits flags{};
28 u64 value{};
29
30protected:
31 // Default constructor
32 QueryBase() = default;
33
34 // Parameterized constructor
35 QueryBase(VAddr address, QueryFlagBits flags_, u64 value_)
36 : guest_address(address), flags(flags_), value{value_} {}
37};
38
39class GuestQuery : public QueryBase {
40public:
41 // Parameterized constructor
42 GuestQuery(bool isLong, VAddr address, u64 queryValue)
43 : QueryBase(address, QueryFlagBits::IsFinalValueSynced, queryValue) {
44 if (isLong) {
45 flags |= QueryFlagBits::HasTimestamp;
46 }
47 }
48};
49
50class HostQueryBase : public QueryBase {
51public:
52 // Default constructor
53 HostQueryBase() : QueryBase(0, QueryFlagBits::IsHostManaged | QueryFlagBits::IsOrphan, 0) {}
54
55 // Parameterized constructor
56 HostQueryBase(bool has_timestamp, VAddr address)
57 : QueryBase(address, QueryFlagBits::IsHostManaged, 0), start_bank_id{}, size_banks{},
58 start_slot{}, size_slots{} {
59 if (has_timestamp) {
60 flags |= QueryFlagBits::HasTimestamp;
61 }
62 }
63
64 u32 start_bank_id{};
65 u32 size_banks{};
66 size_t start_slot{};
67 size_t size_slots{};
68};
69
70} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h
new file mode 100644
index 000000000..78b42b518
--- /dev/null
+++ b/src/video_core/query_cache/query_cache.h
@@ -0,0 +1,580 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7#include <deque>
8#include <memory>
9#include <mutex>
10#include <unordered_map>
11#include <utility>
12
13#include "common/assert.h"
14#include "common/common_types.h"
15#include "common/logging/log.h"
16#include "common/scope_exit.h"
17#include "common/settings.h"
18#include "core/memory.h"
19#include "video_core/engines/maxwell_3d.h"
20#include "video_core/gpu.h"
21#include "video_core/memory_manager.h"
22#include "video_core/query_cache/bank_base.h"
23#include "video_core/query_cache/query_base.h"
24#include "video_core/query_cache/query_cache_base.h"
25#include "video_core/query_cache/query_stream.h"
26#include "video_core/query_cache/types.h"
27
28namespace VideoCommon {
29
30using Maxwell = Tegra::Engines::Maxwell3D;
31
32struct SyncValuesStruct {
33 VAddr address;
34 u64 value;
35 u64 size;
36
37 static constexpr bool GeneratesBaseBuffer = true;
38};
39
40template <typename Traits>
41class GuestStreamer : public SimpleStreamer<GuestQuery> {
42public:
43 using RuntimeType = typename Traits::RuntimeType;
44
45 GuestStreamer(size_t id_, RuntimeType& runtime_)
46 : SimpleStreamer<GuestQuery>(id_), runtime{runtime_} {}
47
48 virtual ~GuestStreamer() = default;
49
50 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
51 std::optional<u32> subreport = std::nullopt) override {
52 auto new_id = BuildQuery(has_timestamp, address, static_cast<u64>(value));
53 pending_sync.push_back(new_id);
54 return new_id;
55 }
56
57 bool HasPendingSync() const override {
58 return !pending_sync.empty();
59 }
60
61 void SyncWrites() override {
62 if (pending_sync.empty()) {
63 return;
64 }
65 std::vector<SyncValuesStruct> sync_values;
66 sync_values.reserve(pending_sync.size());
67 for (size_t pending_id : pending_sync) {
68 auto& query = slot_queries[pending_id];
69 if (True(query.flags & QueryFlagBits::IsRewritten) ||
70 True(query.flags & QueryFlagBits::IsInvalidated)) {
71 continue;
72 }
73 query.flags |= QueryFlagBits::IsHostSynced;
74 sync_values.emplace_back(SyncValuesStruct{
75 .address = query.guest_address,
76 .value = query.value,
77 .size = static_cast<u64>(True(query.flags & QueryFlagBits::HasTimestamp) ? 8 : 4)});
78 }
79 pending_sync.clear();
80 if (sync_values.size() > 0) {
81 runtime.template SyncValues<SyncValuesStruct>(sync_values);
82 }
83 }
84
85private:
86 RuntimeType& runtime;
87 std::deque<size_t> pending_sync;
88};
89
90template <typename Traits>
91class StubStreamer : public GuestStreamer<Traits> {
92public:
93 using RuntimeType = typename Traits::RuntimeType;
94
95 StubStreamer(size_t id_, RuntimeType& runtime_, u32 stub_value_)
96 : GuestStreamer<Traits>(id_, runtime_), stub_value{stub_value_} {}
97
98 ~StubStreamer() override = default;
99
100 size_t WriteCounter(VAddr address, bool has_timestamp, [[maybe_unused]] u32 value,
101 std::optional<u32> subreport = std::nullopt) override {
102 size_t new_id =
103 GuestStreamer<Traits>::WriteCounter(address, has_timestamp, stub_value, subreport);
104 return new_id;
105 }
106
107private:
108 u32 stub_value;
109};
110
111template <typename Traits>
112struct QueryCacheBase<Traits>::QueryCacheBaseImpl {
113 using RuntimeType = typename Traits::RuntimeType;
114
115 QueryCacheBaseImpl(QueryCacheBase<Traits>* owner_, VideoCore::RasterizerInterface& rasterizer_,
116 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_, Tegra::GPU& gpu_)
117 : owner{owner_}, rasterizer{rasterizer_},
118 cpu_memory{cpu_memory_}, runtime{runtime_}, gpu{gpu_} {
119 streamer_mask = 0;
120 for (size_t i = 0; i < static_cast<size_t>(QueryType::MaxQueryTypes); i++) {
121 streamers[i] = runtime.GetStreamerInterface(static_cast<QueryType>(i));
122 if (streamers[i]) {
123 streamer_mask |= 1ULL << streamers[i]->GetId();
124 }
125 }
126 }
127
128 template <typename Func>
129 void ForEachStreamerIn(u64 mask, Func&& func) {
130 static constexpr bool RETURNS_BOOL =
131 std::is_same_v<std::invoke_result<Func, StreamerInterface*>, bool>;
132 while (mask != 0) {
133 size_t position = std::countr_zero(mask);
134 mask &= ~(1ULL << position);
135 if constexpr (RETURNS_BOOL) {
136 if (func(streamers[position])) {
137 return;
138 }
139 } else {
140 func(streamers[position]);
141 }
142 }
143 }
144
145 template <typename Func>
146 void ForEachStreamer(Func&& func) {
147 ForEachStreamerIn(streamer_mask, func);
148 }
149
150 QueryBase* ObtainQuery(QueryCacheBase<Traits>::QueryLocation location) {
151 size_t which_stream = location.stream_id.Value();
152 auto* streamer = streamers[which_stream];
153 if (!streamer) {
154 return nullptr;
155 }
156 return streamer->GetQuery(location.query_id.Value());
157 }
158
159 QueryCacheBase<Traits>* owner;
160 VideoCore::RasterizerInterface& rasterizer;
161 Core::Memory::Memory& cpu_memory;
162 RuntimeType& runtime;
163 Tegra::GPU& gpu;
164 std::array<StreamerInterface*, static_cast<size_t>(QueryType::MaxQueryTypes)> streamers;
165 u64 streamer_mask;
166 std::mutex flush_guard;
167 std::deque<u64> flushes_pending;
168 std::vector<QueryCacheBase<Traits>::QueryLocation> pending_unregister;
169};
170
171template <typename Traits>
172QueryCacheBase<Traits>::QueryCacheBase(Tegra::GPU& gpu_,
173 VideoCore::RasterizerInterface& rasterizer_,
174 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_)
175 : cached_queries{} {
176 impl = std::make_unique<QueryCacheBase<Traits>::QueryCacheBaseImpl>(
177 this, rasterizer_, cpu_memory_, runtime_, gpu_);
178}
179
180template <typename Traits>
181QueryCacheBase<Traits>::~QueryCacheBase() = default;
182
183template <typename Traits>
184void QueryCacheBase<Traits>::CounterEnable(QueryType counter_type, bool is_enabled) {
185 size_t index = static_cast<size_t>(counter_type);
186 StreamerInterface* streamer = impl->streamers[index];
187 if (!streamer) [[unlikely]] {
188 UNREACHABLE();
189 return;
190 }
191 if (is_enabled) {
192 streamer->StartCounter();
193 } else {
194 streamer->PauseCounter();
195 }
196}
197
198template <typename Traits>
199void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
200 size_t index = static_cast<size_t>(counter_type);
201 StreamerInterface* streamer = impl->streamers[index];
202 if (!streamer) [[unlikely]] {
203 UNREACHABLE();
204 return;
205 }
206 streamer->CloseCounter();
207}
208
209template <typename Traits>
210void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
211 size_t index = static_cast<size_t>(counter_type);
212 StreamerInterface* streamer = impl->streamers[index];
213 if (!streamer) [[unlikely]] {
214 UNIMPLEMENTED();
215 return;
216 }
217 streamer->ResetCounter();
218}
219
220template <typename Traits>
221void QueryCacheBase<Traits>::BindToChannel(s32 id) {
222 VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>::BindToChannel(id);
223 impl->runtime.Bind3DEngine(maxwell3d);
224}
225
226template <typename Traits>
227void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type,
228 QueryPropertiesFlags flags, u32 payload, u32 subreport) {
229 const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout);
230 const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence);
231 size_t streamer_id = static_cast<size_t>(counter_type);
232 auto* streamer = impl->streamers[streamer_id];
233 if (streamer == nullptr) [[unlikely]] {
234 counter_type = QueryType::Payload;
235 payload = 1U;
236 streamer_id = static_cast<size_t>(counter_type);
237 streamer = impl->streamers[streamer_id];
238 }
239 auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(addr);
240 if (!cpu_addr_opt) [[unlikely]] {
241 return;
242 }
243 VAddr cpu_addr = *cpu_addr_opt;
244 const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport);
245 auto* query = streamer->GetQuery(new_query_id);
246 if (is_fence) {
247 query->flags |= QueryFlagBits::IsFence;
248 }
249 QueryLocation query_location{};
250 query_location.stream_id.Assign(static_cast<u32>(streamer_id));
251 query_location.query_id.Assign(static_cast<u32>(new_query_id));
252 const auto gen_caching_indexing = [](VAddr cur_addr) {
253 return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
254 static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
255 };
256 u8* pointer = impl->cpu_memory.GetPointer(cpu_addr);
257 u8* pointer_timestamp = impl->cpu_memory.GetPointer(cpu_addr + 8);
258 bool is_synced = !Settings::IsGPULevelHigh() && is_fence;
259
260 std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
261 pointer, pointer_timestamp] {
262 if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
263 if (!is_synced) [[likely]] {
264 impl->pending_unregister.push_back(query_location);
265 }
266 return;
267 }
268 if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] {
269 UNREACHABLE();
270 return;
271 }
272 query_base->value += streamer->GetAmmendValue();
273 streamer->SetAccumulationValue(query_base->value);
274 if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
275 u64 timestamp = impl->gpu.GetTicks();
276 std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
277 std::memcpy(pointer, &query_base->value, sizeof(query_base->value));
278 } else {
279 u32 value = static_cast<u32>(query_base->value);
280 std::memcpy(pointer, &value, sizeof(value));
281 }
282 if (!is_synced) [[likely]] {
283 impl->pending_unregister.push_back(query_location);
284 }
285 });
286 if (is_fence) {
287 impl->rasterizer.SignalFence(std::move(operation));
288 } else {
289 if (!Settings::IsGPULevelHigh() && counter_type == QueryType::Payload) {
290 if (has_timestamp) {
291 u64 timestamp = impl->gpu.GetTicks();
292 u64 value = static_cast<u64>(payload);
293 std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
294 std::memcpy(pointer, &value, sizeof(value));
295 } else {
296 std::memcpy(pointer, &payload, sizeof(payload));
297 }
298 streamer->Free(new_query_id);
299 return;
300 }
301 impl->rasterizer.SyncOperation(std::move(operation));
302 }
303 if (is_synced) {
304 streamer->Free(new_query_id);
305 return;
306 }
307 auto [cont_addr, base] = gen_caching_indexing(cpu_addr);
308 {
309 std::scoped_lock lock(cache_mutex);
310 auto it1 = cached_queries.try_emplace(cont_addr);
311 auto& sub_container = it1.first->second;
312 auto it_current = sub_container.find(base);
313 if (it_current == sub_container.end()) {
314 sub_container.insert_or_assign(base, query_location);
315 return;
316 }
317 auto* old_query = impl->ObtainQuery(it_current->second);
318 old_query->flags |= QueryFlagBits::IsRewritten;
319 sub_container.insert_or_assign(base, query_location);
320 }
321}
322
323template <typename Traits>
324void QueryCacheBase<Traits>::UnregisterPending() {
325 const auto gen_caching_indexing = [](VAddr cur_addr) {
326 return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
327 static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
328 };
329 std::scoped_lock lock(cache_mutex);
330 for (QueryLocation loc : impl->pending_unregister) {
331 const auto [streamer_id, query_id] = loc.unpack();
332 auto* streamer = impl->streamers[streamer_id];
333 if (!streamer) [[unlikely]] {
334 continue;
335 }
336 auto* query = streamer->GetQuery(query_id);
337 auto [cont_addr, base] = gen_caching_indexing(query->guest_address);
338 auto it1 = cached_queries.find(cont_addr);
339 if (it1 != cached_queries.end()) {
340 auto it2 = it1->second.find(base);
341 if (it2 != it1->second.end()) {
342 if (it2->second.raw == loc.raw) {
343 it1->second.erase(it2);
344 }
345 }
346 }
347 streamer->Free(query_id);
348 }
349 impl->pending_unregister.clear();
350}
351
352template <typename Traits>
353void QueryCacheBase<Traits>::NotifyWFI() {
354 bool should_sync = false;
355 impl->ForEachStreamer(
356 [&should_sync](StreamerInterface* streamer) { should_sync |= streamer->HasPendingSync(); });
357 if (!should_sync) {
358 return;
359 }
360
361 impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->PresyncWrites(); });
362 impl->runtime.Barriers(true);
363 impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->SyncWrites(); });
364 impl->runtime.Barriers(false);
365}
366
367template <typename Traits>
368void QueryCacheBase<Traits>::NotifySegment(bool resume) {
369 if (resume) {
370 impl->runtime.ResumeHostConditionalRendering();
371 } else {
372 CounterClose(VideoCommon::QueryType::ZPassPixelCount64);
373 CounterClose(VideoCommon::QueryType::StreamingByteCount);
374 impl->runtime.PauseHostConditionalRendering();
375 }
376}
377
378template <typename Traits>
379bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
380 bool qc_dirty = false;
381 const auto gen_lookup = [this, &qc_dirty](GPUVAddr address) -> VideoCommon::LookupData {
382 auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(address);
383 if (!cpu_addr_opt) [[unlikely]] {
384 return VideoCommon::LookupData{
385 .address = 0,
386 .found_query = nullptr,
387 };
388 }
389 VAddr cpu_addr = *cpu_addr_opt;
390 std::scoped_lock lock(cache_mutex);
391 auto it1 = cached_queries.find(cpu_addr >> Core::Memory::YUZU_PAGEBITS);
392 if (it1 == cached_queries.end()) {
393 return VideoCommon::LookupData{
394 .address = cpu_addr,
395 .found_query = nullptr,
396 };
397 }
398 auto& sub_container = it1->second;
399 auto it_current = sub_container.find(cpu_addr & Core::Memory::YUZU_PAGEMASK);
400
401 if (it_current == sub_container.end()) {
402 auto it_current_2 = sub_container.find((cpu_addr & Core::Memory::YUZU_PAGEMASK) + 4);
403 if (it_current_2 == sub_container.end()) {
404 return VideoCommon::LookupData{
405 .address = cpu_addr,
406 .found_query = nullptr,
407 };
408 }
409 }
410 auto* query = impl->ObtainQuery(it_current->second);
411 qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) &&
412 False(query->flags & QueryFlagBits::IsGuestSynced);
413 return VideoCommon::LookupData{
414 .address = cpu_addr,
415 .found_query = query,
416 };
417 };
418
419 auto& regs = maxwell3d->regs;
420 if (regs.render_enable_override != Maxwell::Regs::RenderEnable::Override::UseRenderEnable) {
421 impl->runtime.EndHostConditionalRendering();
422 return false;
423 }
424 const ComparisonMode mode = static_cast<ComparisonMode>(regs.render_enable.mode);
425 const GPUVAddr address = regs.render_enable.Address();
426 switch (mode) {
427 case ComparisonMode::True:
428 impl->runtime.EndHostConditionalRendering();
429 return false;
430 case ComparisonMode::False:
431 impl->runtime.EndHostConditionalRendering();
432 return false;
433 case ComparisonMode::Conditional: {
434 VideoCommon::LookupData object_1{gen_lookup(address)};
435 return impl->runtime.HostConditionalRenderingCompareValue(object_1, qc_dirty);
436 }
437 case ComparisonMode::IfEqual: {
438 VideoCommon::LookupData object_1{gen_lookup(address)};
439 VideoCommon::LookupData object_2{gen_lookup(address + 16)};
440 return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
441 true);
442 }
443 case ComparisonMode::IfNotEqual: {
444 VideoCommon::LookupData object_1{gen_lookup(address)};
445 VideoCommon::LookupData object_2{gen_lookup(address + 16)};
446 return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
447 false);
448 }
449 default:
450 return false;
451 }
452}
453
454// Async downloads
455template <typename Traits>
456void QueryCacheBase<Traits>::CommitAsyncFlushes() {
457 // Make sure to have the results synced in Host.
458 NotifyWFI();
459
460 u64 mask{};
461 {
462 std::scoped_lock lk(impl->flush_guard);
463 impl->ForEachStreamer([&mask](StreamerInterface* streamer) {
464 bool local_result = streamer->HasUnsyncedQueries();
465 if (local_result) {
466 mask |= 1ULL << streamer->GetId();
467 }
468 });
469 impl->flushes_pending.push_back(mask);
470 }
471 std::function<void()> func([this] { UnregisterPending(); });
472 impl->rasterizer.SyncOperation(std::move(func));
473 if (mask == 0) {
474 return;
475 }
476 u64 ran_mask = ~mask;
477 while (mask) {
478 impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
479 u64 dep_mask = streamer->GetDependentMask();
480 if ((dep_mask & ~ran_mask) != 0) {
481 return;
482 }
483 u64 index = streamer->GetId();
484 ran_mask |= (1ULL << index);
485 mask &= ~(1ULL << index);
486 streamer->PushUnsyncedQueries();
487 });
488 }
489}
490
491template <typename Traits>
492bool QueryCacheBase<Traits>::HasUncommittedFlushes() const {
493 bool result = false;
494 impl->ForEachStreamer([&result](StreamerInterface* streamer) {
495 result |= streamer->HasUnsyncedQueries();
496 return result;
497 });
498 return result;
499}
500
501template <typename Traits>
502bool QueryCacheBase<Traits>::ShouldWaitAsyncFlushes() {
503 std::scoped_lock lk(impl->flush_guard);
504 return !impl->flushes_pending.empty() && impl->flushes_pending.front() != 0ULL;
505}
506
507template <typename Traits>
508void QueryCacheBase<Traits>::PopAsyncFlushes() {
509 u64 mask;
510 {
511 std::scoped_lock lk(impl->flush_guard);
512 mask = impl->flushes_pending.front();
513 impl->flushes_pending.pop_front();
514 }
515 if (mask == 0) {
516 return;
517 }
518 u64 ran_mask = ~mask;
519 while (mask) {
520 impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
521 u64 dep_mask = streamer->GetDependenceMask();
522 if ((dep_mask & ~ran_mask) != 0) {
523 return;
524 }
525 u64 index = streamer->GetId();
526 ran_mask |= (1ULL << index);
527 mask &= ~(1ULL << index);
528 streamer->PopUnsyncedQueries();
529 });
530 }
531}
532
533// Invalidation
534
535template <typename Traits>
536void QueryCacheBase<Traits>::InvalidateQuery(QueryCacheBase<Traits>::QueryLocation location) {
537 auto* query_base = impl->ObtainQuery(location);
538 if (!query_base) {
539 return;
540 }
541 query_base->flags |= QueryFlagBits::IsInvalidated;
542}
543
544template <typename Traits>
545bool QueryCacheBase<Traits>::IsQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
546 auto* query_base = impl->ObtainQuery(location);
547 if (!query_base) {
548 return false;
549 }
550 return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
551 False(query_base->flags & QueryFlagBits::IsGuestSynced);
552}
553
554template <typename Traits>
555bool QueryCacheBase<Traits>::SemiFlushQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
556 auto* query_base = impl->ObtainQuery(location);
557 if (!query_base) {
558 return false;
559 }
560 if (True(query_base->flags & QueryFlagBits::IsFinalValueSynced) &&
561 False(query_base->flags & QueryFlagBits::IsGuestSynced)) {
562 auto* ptr = impl->cpu_memory.GetPointer(query_base->guest_address);
563 if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
564 std::memcpy(ptr, &query_base->value, sizeof(query_base->value));
565 return false;
566 }
567 u32 value_l = static_cast<u32>(query_base->value);
568 std::memcpy(ptr, &value_l, sizeof(value_l));
569 return false;
570 }
571 return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
572 False(query_base->flags & QueryFlagBits::IsGuestSynced);
573}
574
575template <typename Traits>
576void QueryCacheBase<Traits>::RequestGuestHostSync() {
577 impl->rasterizer.ReleaseFences();
578}
579
580} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h
new file mode 100644
index 000000000..07be421c6
--- /dev/null
+++ b/src/video_core/query_cache/query_cache_base.h
@@ -0,0 +1,181 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <mutex>
8#include <optional>
9#include <span>
10#include <unordered_map>
11#include <utility>
12
13#include "common/assert.h"
14#include "common/bit_field.h"
15#include "common/common_types.h"
16#include "core/memory.h"
17#include "video_core/control/channel_state_cache.h"
18#include "video_core/query_cache/query_base.h"
19#include "video_core/query_cache/types.h"
20
21namespace Core::Memory {
22class Memory;
23}
24
25namespace VideoCore {
26class RasterizerInterface;
27}
28
29namespace Tegra {
30class GPU;
31}
32
33namespace VideoCommon {
34
35struct LookupData {
36 VAddr address;
37 QueryBase* found_query;
38};
39
40template <typename Traits>
41class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
42 using RuntimeType = typename Traits::RuntimeType;
43
44public:
45 union QueryLocation {
46 BitField<27, 5, u32> stream_id;
47 BitField<0, 27, u32> query_id;
48 u32 raw;
49
50 std::pair<size_t, size_t> unpack() const {
51 return {static_cast<size_t>(stream_id.Value()), static_cast<size_t>(query_id.Value())};
52 }
53 };
54
55 explicit QueryCacheBase(Tegra::GPU& gpu, VideoCore::RasterizerInterface& rasterizer_,
56 Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_);
57
58 ~QueryCacheBase();
59
60 void InvalidateRegion(VAddr addr, std::size_t size) {
61 IterateCache<true>(addr, size,
62 [this](QueryLocation location) { InvalidateQuery(location); });
63 }
64
65 void FlushRegion(VAddr addr, std::size_t size) {
66 bool result = false;
67 IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
68 result |= SemiFlushQueryDirty(location);
69 return result;
70 });
71 if (result) {
72 RequestGuestHostSync();
73 }
74 }
75
76 static u64 BuildMask(std::span<const QueryType> types) {
77 u64 mask = 0;
78 for (auto query_type : types) {
79 mask |= 1ULL << (static_cast<u64>(query_type));
80 }
81 return mask;
82 }
83
84 /// Return true when a CPU region is modified from the GPU
85 [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size) {
86 bool result = false;
87 IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
88 result |= IsQueryDirty(location);
89 return result;
90 });
91 return result;
92 }
93
94 void CounterEnable(QueryType counter_type, bool is_enabled);
95
96 void CounterReset(QueryType counter_type);
97
98 void CounterClose(QueryType counter_type);
99
100 void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
101 u32 payload, u32 subreport);
102
103 void NotifyWFI();
104
105 bool AccelerateHostConditionalRendering();
106
107 // Async downloads
108 void CommitAsyncFlushes();
109
110 bool HasUncommittedFlushes() const;
111
112 bool ShouldWaitAsyncFlushes();
113
114 void PopAsyncFlushes();
115
116 void NotifySegment(bool resume);
117
118 void BindToChannel(s32 id) override;
119
120protected:
121 template <bool remove_from_cache, typename Func>
122 void IterateCache(VAddr addr, std::size_t size, Func&& func) {
123 static constexpr bool RETURNS_BOOL =
124 std::is_same_v<std::invoke_result<Func, QueryLocation>, bool>;
125 const u64 addr_begin = addr;
126 const u64 addr_end = addr_begin + size;
127
128 const u64 page_end = addr_end >> Core::Memory::YUZU_PAGEBITS;
129 std::scoped_lock lock(cache_mutex);
130 for (u64 page = addr_begin >> Core::Memory::YUZU_PAGEBITS; page <= page_end; ++page) {
131 const u64 page_start = page << Core::Memory::YUZU_PAGEBITS;
132 const auto in_range = [page_start, addr_begin, addr_end](const u32 query_location) {
133 const u64 cache_begin = page_start + query_location;
134 const u64 cache_end = cache_begin + sizeof(u32);
135 return cache_begin < addr_end && addr_begin < cache_end;
136 };
137 const auto& it = cached_queries.find(page);
138 if (it == std::end(cached_queries)) {
139 continue;
140 }
141 auto& contents = it->second;
142 for (auto& query : contents) {
143 if (!in_range(query.first)) {
144 continue;
145 }
146 if constexpr (RETURNS_BOOL) {
147 if (func(query.second)) {
148 return;
149 }
150 } else {
151 func(query.second);
152 }
153 }
154 if constexpr (remove_from_cache) {
155 const auto in_range2 = [&](const std::pair<u32, QueryLocation>& pair) {
156 return in_range(pair.first);
157 };
158 std::erase_if(contents, in_range2);
159 }
160 }
161 }
162
163 using ContentCache = std::unordered_map<u64, std::unordered_map<u32, QueryLocation>>;
164
165 void InvalidateQuery(QueryLocation location);
166 bool IsQueryDirty(QueryLocation location);
167 bool SemiFlushQueryDirty(QueryLocation location);
168 void RequestGuestHostSync();
169 void UnregisterPending();
170
171 std::unordered_map<u64, std::unordered_map<u32, QueryLocation>> cached_queries;
172 std::mutex cache_mutex;
173
174 struct QueryCacheBaseImpl;
175 friend struct QueryCacheBaseImpl;
176 friend RuntimeType;
177
178 std::unique_ptr<QueryCacheBaseImpl> impl;
179};
180
181} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_stream.h b/src/video_core/query_cache/query_stream.h
new file mode 100644
index 000000000..39da6ac07
--- /dev/null
+++ b/src/video_core/query_cache/query_stream.h
@@ -0,0 +1,149 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <deque>
7#include <optional>
8#include <vector>
9
10#include "common/assert.h"
11#include "common/common_types.h"
12#include "video_core/query_cache/bank_base.h"
13#include "video_core/query_cache/query_base.h"
14
15namespace VideoCommon {
16
17class StreamerInterface {
18public:
19 explicit StreamerInterface(size_t id_) : id{id_}, dependence_mask{}, dependent_mask{} {}
20 virtual ~StreamerInterface() = default;
21
22 virtual QueryBase* GetQuery(size_t id) = 0;
23
24 virtual void StartCounter() {
25 /* Do Nothing */
26 }
27
28 virtual void PauseCounter() {
29 /* Do Nothing */
30 }
31
32 virtual void ResetCounter() {
33 /* Do Nothing */
34 }
35
36 virtual void CloseCounter() {
37 /* Do Nothing */
38 }
39
40 virtual bool HasPendingSync() const {
41 return false;
42 }
43
44 virtual void PresyncWrites() {
45 /* Do Nothing */
46 }
47
48 virtual void SyncWrites() {
49 /* Do Nothing */
50 }
51
52 virtual size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
53 std::optional<u32> subreport = std::nullopt) = 0;
54
55 virtual bool HasUnsyncedQueries() const {
56 return false;
57 }
58
59 virtual void PushUnsyncedQueries() {
60 /* Do Nothing */
61 }
62
63 virtual void PopUnsyncedQueries() {
64 /* Do Nothing */
65 }
66
67 virtual void Free(size_t query_id) = 0;
68
69 size_t GetId() const {
70 return id;
71 }
72
73 u64 GetDependenceMask() const {
74 return dependence_mask;
75 }
76
77 u64 GetDependentMask() const {
78 return dependence_mask;
79 }
80
81 u64 GetAmmendValue() const {
82 return ammend_value;
83 }
84
85 void SetAccumulationValue(u64 new_value) {
86 acumulation_value = new_value;
87 }
88
89protected:
90 void MakeDependent(StreamerInterface* depend_on) {
91 dependence_mask |= 1ULL << depend_on->id;
92 depend_on->dependent_mask |= 1ULL << id;
93 }
94
95 const size_t id;
96 u64 dependence_mask;
97 u64 dependent_mask;
98 u64 ammend_value{};
99 u64 acumulation_value{};
100};
101
102template <typename QueryType>
103class SimpleStreamer : public StreamerInterface {
104public:
105 explicit SimpleStreamer(size_t id_) : StreamerInterface{id_} {}
106 virtual ~SimpleStreamer() = default;
107
108protected:
109 virtual QueryType* GetQuery(size_t query_id) override {
110 if (query_id < slot_queries.size()) {
111 return &slot_queries[query_id];
112 }
113 return nullptr;
114 }
115
116 virtual void Free(size_t query_id) override {
117 std::scoped_lock lk(guard);
118 ReleaseQuery(query_id);
119 }
120
121 template <typename... Args, typename = decltype(QueryType(std::declval<Args>()...))>
122 size_t BuildQuery(Args&&... args) {
123 std::scoped_lock lk(guard);
124 if (!old_queries.empty()) {
125 size_t new_id = old_queries.front();
126 old_queries.pop_front();
127 new (&slot_queries[new_id]) QueryType(std::forward<Args>(args)...);
128 return new_id;
129 }
130 size_t new_id = slot_queries.size();
131 slot_queries.emplace_back(std::forward<Args>(args)...);
132 return new_id;
133 }
134
135 void ReleaseQuery(size_t query_id) {
136
137 if (query_id < slot_queries.size()) {
138 old_queries.push_back(query_id);
139 return;
140 }
141 UNREACHABLE();
142 }
143
144 std::mutex guard;
145 std::deque<QueryType> slot_queries;
146 std::deque<size_t> old_queries;
147};
148
149} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/types.h b/src/video_core/query_cache/types.h
new file mode 100644
index 000000000..e9226bbfc
--- /dev/null
+++ b/src/video_core/query_cache/types.h
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace VideoCommon {
10
11enum class QueryPropertiesFlags : u32 {
12 HasTimeout = 1 << 0,
13 IsAFence = 1 << 1,
14};
15DECLARE_ENUM_FLAG_OPERATORS(QueryPropertiesFlags)
16
17// This should always be equivalent to maxwell3d Report Semaphore Reports
18enum class QueryType : u32 {
19 Payload = 0, // "None" in docs, but confirmed via hardware to return the payload
20 VerticesGenerated = 1,
21 ZPassPixelCount = 2,
22 PrimitivesGenerated = 3,
23 AlphaBetaClocks = 4,
24 VertexShaderInvocations = 5,
25 StreamingPrimitivesNeededMinusSucceeded = 6,
26 GeometryShaderInvocations = 7,
27 GeometryShaderPrimitivesGenerated = 9,
28 ZCullStats0 = 10,
29 StreamingPrimitivesSucceeded = 11,
30 ZCullStats1 = 12,
31 StreamingPrimitivesNeeded = 13,
32 ZCullStats2 = 14,
33 ClipperInvocations = 15,
34 ZCullStats3 = 16,
35 ClipperPrimitivesGenerated = 17,
36 VtgPrimitivesOut = 18,
37 PixelShaderInvocations = 19,
38 ZPassPixelCount64 = 21,
39 IEEECleanColorTarget = 24,
40 IEEECleanZetaTarget = 25,
41 StreamingByteCount = 26,
42 TessellationInitInvocations = 27,
43 BoundingRectangle = 28,
44 TessellationShaderInvocations = 29,
45 TotalStreamingPrimitivesNeededMinusSucceeded = 30,
46 TessellationShaderPrimitivesGenerated = 31,
47 // max.
48 MaxQueryTypes,
49};
50
51// Comparison modes for Host Conditional Rendering
52enum class ComparisonMode : u32 {
53 False = 0,
54 True = 1,
55 Conditional = 2,
56 IfEqual = 3,
57 IfNotEqual = 4,
58 MaxComparisonMode,
59};
60
61// Reduction ops.
62enum class ReductionOp : u32 {
63 RedAdd = 0,
64 RedMin = 1,
65 RedMax = 2,
66 RedInc = 3,
67 RedDec = 4,
68 RedAnd = 5,
69 RedOr = 6,
70 RedXor = 7,
71 MaxReductionOp,
72};
73
74} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index cb8029a4f..af1469147 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -12,6 +12,7 @@
12#include "video_core/cache_types.h" 12#include "video_core/cache_types.h"
13#include "video_core/engines/fermi_2d.h" 13#include "video_core/engines/fermi_2d.h"
14#include "video_core/gpu.h" 14#include "video_core/gpu.h"
15#include "video_core/query_cache/types.h"
15#include "video_core/rasterizer_download_area.h" 16#include "video_core/rasterizer_download_area.h"
16 17
17namespace Tegra { 18namespace Tegra {
@@ -26,11 +27,6 @@ struct ChannelState;
26 27
27namespace VideoCore { 28namespace VideoCore {
28 29
29enum class QueryType {
30 SamplesPassed,
31};
32constexpr std::size_t NumQueryTypes = 1;
33
34enum class LoadCallbackStage { 30enum class LoadCallbackStage {
35 Prepare, 31 Prepare,
36 Build, 32 Build,
@@ -58,10 +54,11 @@ public:
58 virtual void DispatchCompute() = 0; 54 virtual void DispatchCompute() = 0;
59 55
60 /// Resets the counter of a query 56 /// Resets the counter of a query
61 virtual void ResetCounter(QueryType type) = 0; 57 virtual void ResetCounter(VideoCommon::QueryType type) = 0;
62 58
63 /// Records a GPU query and caches it 59 /// Records a GPU query and caches it
64 virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0; 60 virtual void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
61 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) = 0;
65 62
66 /// Signal an uniform buffer binding 63 /// Signal an uniform buffer binding
67 virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 64 virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -83,7 +80,7 @@ public:
83 virtual void SignalReference() = 0; 80 virtual void SignalReference() = 0;
84 81
85 /// Release all pending fences. 82 /// Release all pending fences.
86 virtual void ReleaseFences() = 0; 83 virtual void ReleaseFences(bool force = true) = 0;
87 84
88 /// Notify rasterizer that all caches should be flushed to Switch memory 85 /// Notify rasterizer that all caches should be flushed to Switch memory
89 virtual void FlushAll() = 0; 86 virtual void FlushAll() = 0;
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index 92ecf6682..65cd5aa06 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -26,16 +26,18 @@ void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {}
26void RasterizerNull::DrawTexture() {} 26void RasterizerNull::DrawTexture() {}
27void RasterizerNull::Clear(u32 layer_count) {} 27void RasterizerNull::Clear(u32 layer_count) {}
28void RasterizerNull::DispatchCompute() {} 28void RasterizerNull::DispatchCompute() {}
29void RasterizerNull::ResetCounter(VideoCore::QueryType type) {} 29void RasterizerNull::ResetCounter(VideoCommon::QueryType type) {}
30void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 30void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
31 std::optional<u64> timestamp) { 31 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
32 if (!gpu_memory) { 32 if (!gpu_memory) {
33 return; 33 return;
34 } 34 }
35 35 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
36 gpu_memory->Write(gpu_addr, u64{0}); 36 u64 ticks = m_gpu.GetTicks();
37 if (timestamp) { 37 gpu_memory->Write<u64>(gpu_addr + 8, ticks);
38 gpu_memory->Write(gpu_addr + 8, *timestamp); 38 gpu_memory->Write<u64>(gpu_addr, static_cast<u64>(payload));
39 } else {
40 gpu_memory->Write<u32>(gpu_addr, payload);
39 } 41 }
40} 42}
41void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 43void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -74,7 +76,7 @@ void RasterizerNull::SignalSyncPoint(u32 value) {
74 syncpoint_manager.IncrementHost(value); 76 syncpoint_manager.IncrementHost(value);
75} 77}
76void RasterizerNull::SignalReference() {} 78void RasterizerNull::SignalReference() {}
77void RasterizerNull::ReleaseFences() {} 79void RasterizerNull::ReleaseFences(bool) {}
78void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} 80void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
79void RasterizerNull::WaitForIdle() {} 81void RasterizerNull::WaitForIdle() {}
80void RasterizerNull::FragmentBarrier() {} 82void RasterizerNull::FragmentBarrier() {}
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index 93b9a6971..23001eeb8 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -42,8 +42,9 @@ public:
42 void DrawTexture() override; 42 void DrawTexture() override;
43 void Clear(u32 layer_count) override; 43 void Clear(u32 layer_count) override;
44 void DispatchCompute() override; 44 void DispatchCompute() override;
45 void ResetCounter(VideoCore::QueryType type) override; 45 void ResetCounter(VideoCommon::QueryType type) override;
46 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 46 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
47 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
47 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 48 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
48 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 49 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
49 void FlushAll() override; 50 void FlushAll() override;
@@ -63,7 +64,7 @@ public:
63 void SyncOperation(std::function<void()>&& func) override; 64 void SyncOperation(std::function<void()>&& func) override;
64 void SignalSyncPoint(u32 value) override; 65 void SignalSyncPoint(u32 value) override;
65 void SignalReference() override; 66 void SignalReference() override;
66 void ReleaseFences() override; 67 void ReleaseFences(bool force) override;
67 void FlushAndInvalidateRegion( 68 void FlushAndInvalidateRegion(
68 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 69 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
69 void WaitForIdle() override; 70 void WaitForIdle() override;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index 99d7347f5..ec142d48e 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -27,7 +27,7 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
27} // Anonymous namespace 27} // Anonymous namespace
28 28
29QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_) 29QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
30 : QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {} 30 : QueryCacheLegacy(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
31 31
32QueryCache::~QueryCache() = default; 32QueryCache::~QueryCache() = default;
33 33
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index 872513f22..0721e0b3d 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,7 +26,7 @@ class RasterizerOpenGL;
26using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; 26using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
27 27
28class QueryCache final 28class QueryCache final
29 : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { 29 : public VideoCommon::QueryCacheLegacy<QueryCache, CachedQuery, CounterStream, HostCounter> {
30public: 30public:
31 explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_); 31 explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
32 ~QueryCache(); 32 ~QueryCache();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index dd03efecd..27e2de1bf 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -396,13 +396,39 @@ void RasterizerOpenGL::DispatchCompute() {
396 has_written_global_memory |= pipeline->WritesGlobalMemory(); 396 has_written_global_memory |= pipeline->WritesGlobalMemory();
397} 397}
398 398
399void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) { 399void RasterizerOpenGL::ResetCounter(VideoCommon::QueryType type) {
400 query_cache.ResetCounter(type); 400 if (type == VideoCommon::QueryType::ZPassPixelCount64) {
401 query_cache.ResetCounter(VideoCore::QueryType::SamplesPassed);
402 }
401} 403}
402 404
403void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 405void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
404 std::optional<u64> timestamp) { 406 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
405 query_cache.Query(gpu_addr, type, timestamp); 407 if (type == VideoCommon::QueryType::ZPassPixelCount64) {
408 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
409 query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, {gpu.GetTicks()});
410 } else {
411 query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, std::nullopt);
412 }
413 return;
414 }
415 if (type != VideoCommon::QueryType::Payload) {
416 payload = 1u;
417 }
418 std::function<void()> func([this, gpu_addr, flags, memory_manager = gpu_memory, payload]() {
419 if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
420 u64 ticks = gpu.GetTicks();
421 memory_manager->Write<u64>(gpu_addr + 8, ticks);
422 memory_manager->Write<u64>(gpu_addr, static_cast<u64>(payload));
423 } else {
424 memory_manager->Write<u32>(gpu_addr, payload);
425 }
426 });
427 if (True(flags & VideoCommon::QueryPropertiesFlags::IsAFence)) {
428 SignalFence(std::move(func));
429 return;
430 }
431 func();
406} 432}
407 433
408void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 434void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -573,8 +599,8 @@ void RasterizerOpenGL::SignalReference() {
573 fence_manager.SignalOrdering(); 599 fence_manager.SignalOrdering();
574} 600}
575 601
576void RasterizerOpenGL::ReleaseFences() { 602void RasterizerOpenGL::ReleaseFences(bool force) {
577 fence_manager.WaitPendingFences(); 603 fence_manager.WaitPendingFences(force);
578} 604}
579 605
580void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size, 606void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size,
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 8eda2ddba..ceffe1f1e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -86,8 +86,9 @@ public:
86 void DrawTexture() override; 86 void DrawTexture() override;
87 void Clear(u32 layer_count) override; 87 void Clear(u32 layer_count) override;
88 void DispatchCompute() override; 88 void DispatchCompute() override;
89 void ResetCounter(VideoCore::QueryType type) override; 89 void ResetCounter(VideoCommon::QueryType type) override;
90 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 90 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
91 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
91 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 92 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
92 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 93 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
93 void FlushAll() override; 94 void FlushAll() override;
@@ -107,7 +108,7 @@ public:
107 void SyncOperation(std::function<void()>&& func) override; 108 void SyncOperation(std::function<void()>&& func) override;
108 void SignalSyncPoint(u32 value) override; 109 void SignalSyncPoint(u32 value) override;
109 void SignalReference() override; 110 void SignalReference() override;
110 void ReleaseFences() override; 111 void ReleaseFences(bool force = true) override;
111 void FlushAndInvalidateRegion( 112 void FlushAndInvalidateRegion(
112 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 113 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
113 void WaitForIdle() override; 114 void WaitForIdle() override;
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 35bf80ea3..208e88533 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -185,7 +185,7 @@ struct FormatTuple {
185 {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB 185 {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
186 {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB 186 {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
187 {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB 187 {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
188 {VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM 188 {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM
189 {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM 189 {VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM
190 {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB 190 {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
191 {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB 191 {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index e15865d16..d8148e89a 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -61,6 +61,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
61 if (device.IsExtTransformFeedbackSupported()) { 61 if (device.IsExtTransformFeedbackSupported()) {
62 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; 62 flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
63 } 63 }
64 if (device.IsExtConditionalRendering()) {
65 flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
66 }
64 const VkBufferCreateInfo buffer_ci = { 67 const VkBufferCreateInfo buffer_ci = {
65 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 68 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
66 .pNext = nullptr, 69 .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 54ee030ce..617f92910 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -3,6 +3,7 @@
3 3
4#include <array> 4#include <array>
5#include <memory> 5#include <memory>
6#include <numeric>
6#include <optional> 7#include <optional>
7#include <utility> 8#include <utility>
8 9
@@ -11,7 +12,13 @@
11#include "common/assert.h" 12#include "common/assert.h"
12#include "common/common_types.h" 13#include "common/common_types.h"
13#include "common/div_ceil.h" 14#include "common/div_ceil.h"
15#include "common/vector_math.h"
14#include "video_core/host_shaders/astc_decoder_comp_spv.h" 16#include "video_core/host_shaders/astc_decoder_comp_spv.h"
17#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h"
18#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h"
19#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
20#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
21#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
15#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" 22#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
16#include "video_core/host_shaders/vulkan_uint8_comp_spv.h" 23#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
17#include "video_core/renderer_vulkan/vk_compute_pass.h" 24#include "video_core/renderer_vulkan/vk_compute_pass.h"
@@ -57,6 +64,30 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 2> INPUT_OUTPUT_DESCRIPTOR_SE
57 }, 64 },
58}}; 65}};
59 66
67constexpr std::array<VkDescriptorSetLayoutBinding, 3> QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS{{
68 {
69 .binding = 0,
70 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
71 .descriptorCount = 1,
72 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
73 .pImmutableSamplers = nullptr,
74 },
75 {
76 .binding = 1,
77 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
78 .descriptorCount = 1,
79 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
80 .pImmutableSamplers = nullptr,
81 },
82 {
83 .binding = 2,
84 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
85 .descriptorCount = 1,
86 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
87 .pImmutableSamplers = nullptr,
88 },
89}};
90
60constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{ 91constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
61 .uniform_buffers = 0, 92 .uniform_buffers = 0,
62 .storage_buffers = 2, 93 .storage_buffers = 2,
@@ -67,6 +98,16 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
67 .score = 2, 98 .score = 2,
68}; 99};
69 100
101constexpr DescriptorBankInfo QUERIES_SCAN_BANK_INFO{
102 .uniform_buffers = 0,
103 .storage_buffers = 3,
104 .texture_buffers = 0,
105 .image_buffers = 0,
106 .textures = 0,
107 .images = 0,
108 .score = 3,
109};
110
70constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{ 111constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{
71 { 112 {
72 .binding = ASTC_BINDING_INPUT_BUFFER, 113 .binding = ASTC_BINDING_INPUT_BUFFER,
@@ -94,6 +135,33 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{
94 .score = 2, 135 .score = 2,
95}; 136};
96 137
138constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> MSAA_DESCRIPTOR_SET_BINDINGS{{
139 {
140 .binding = 0,
141 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
142 .descriptorCount = 1,
143 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
144 .pImmutableSamplers = nullptr,
145 },
146 {
147 .binding = 1,
148 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
149 .descriptorCount = 1,
150 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
151 .pImmutableSamplers = nullptr,
152 },
153}};
154
155constexpr DescriptorBankInfo MSAA_BANK_INFO{
156 .uniform_buffers = 0,
157 .storage_buffers = 0,
158 .texture_buffers = 0,
159 .image_buffers = 0,
160 .textures = 0,
161 .images = 2,
162 .score = 2,
163};
164
97constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ 165constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{
98 .dstBinding = 0, 166 .dstBinding = 0,
99 .dstArrayElement = 0, 167 .dstArrayElement = 0,
@@ -103,6 +171,24 @@ constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLAT
103 .stride = sizeof(DescriptorUpdateEntry), 171 .stride = sizeof(DescriptorUpdateEntry),
104}; 172};
105 173
174constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE{
175 .dstBinding = 0,
176 .dstArrayElement = 0,
177 .descriptorCount = 3,
178 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
179 .offset = 0,
180 .stride = sizeof(DescriptorUpdateEntry),
181};
182
183constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{
184 .dstBinding = 0,
185 .dstArrayElement = 0,
186 .descriptorCount = 2,
187 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
188 .offset = 0,
189 .stride = sizeof(DescriptorUpdateEntry),
190};
191
106constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS> 192constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS>
107 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ 193 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
108 { 194 {
@@ -131,13 +217,21 @@ struct AstcPushConstants {
131 u32 block_height; 217 u32 block_height;
132 u32 block_height_mask; 218 u32 block_height_mask;
133}; 219};
220
221struct QueriesPrefixScanPushConstants {
222 u32 min_accumulation_base;
223 u32 max_accumulation_base;
224 u32 accumulation_limit;
225 u32 buffer_offset;
226};
134} // Anonymous namespace 227} // Anonymous namespace
135 228
136ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, 229ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
137 vk::Span<VkDescriptorSetLayoutBinding> bindings, 230 vk::Span<VkDescriptorSetLayoutBinding> bindings,
138 vk::Span<VkDescriptorUpdateTemplateEntry> templates, 231 vk::Span<VkDescriptorUpdateTemplateEntry> templates,
139 const DescriptorBankInfo& bank_info, 232 const DescriptorBankInfo& bank_info,
140 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code) 233 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
234 std::optional<u32> optional_subgroup_size)
141 : device{device_} { 235 : device{device_} {
142 descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ 236 descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
143 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 237 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
@@ -170,6 +264,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
170 }); 264 });
171 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); 265 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info);
172 } 266 }
267 if (code.empty()) {
268 return;
269 }
173 module = device.GetLogical().CreateShaderModule({ 270 module = device.GetLogical().CreateShaderModule({
174 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 271 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
175 .pNext = nullptr, 272 .pNext = nullptr,
@@ -178,13 +275,19 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
178 .pCode = code.data(), 275 .pCode = code.data(),
179 }); 276 });
180 device.SaveShader(code); 277 device.SaveShader(code);
278 const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
279 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
280 .pNext = nullptr,
281 .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
282 };
283 bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
181 pipeline = device.GetLogical().CreateComputePipeline({ 284 pipeline = device.GetLogical().CreateComputePipeline({
182 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, 285 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
183 .pNext = nullptr, 286 .pNext = nullptr,
184 .flags = 0, 287 .flags = 0,
185 .stage{ 288 .stage{
186 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 289 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
187 .pNext = nullptr, 290 .pNext = use_setup_size ? &subgroup_size_ci : nullptr,
188 .flags = 0, 291 .flags = 0,
189 .stage = VK_SHADER_STAGE_COMPUTE_BIT, 292 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
190 .module = *module, 293 .module = *module,
@@ -302,6 +405,123 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
302 return {staging.buffer, staging.offset}; 405 return {staging.buffer, staging.offset};
303} 406}
304 407
408ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
409 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
410 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
411 : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
412 INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr,
413 RESOLVE_CONDITIONAL_RENDER_COMP_SPV),
414 scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
415
416void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
417 u32 src_offset, bool compare_to_zero) {
418 const size_t compare_size = compare_to_zero ? 8 : 24;
419
420 compute_pass_descriptor_queue.Acquire();
421 compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size);
422 compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32));
423 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
424
425 scheduler.RequestOutsideRenderPassOperationContext();
426 scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) {
427 static constexpr VkMemoryBarrier read_barrier{
428 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
429 .pNext = nullptr,
430 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT,
431 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
432 };
433 static constexpr VkMemoryBarrier write_barrier{
434 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
435 .pNext = nullptr,
436 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
437 .dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
438 };
439 const VkDescriptorSet set = descriptor_allocator.Commit();
440 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
441
442 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
443 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
444 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
445 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
446 cmdbuf.Dispatch(1, 1, 1);
447 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
448 VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
449 });
450}
451
452QueriesPrefixScanPass::QueriesPrefixScanPass(
453 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
454 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
455 : ComputePass(
456 device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
457 QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
458 COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
459 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
460 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
461 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
462 device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
463 ? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
464 : std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
465 scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
466
467void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
468 VkBuffer src_buffer, size_t number_of_sums,
469 size_t min_accumulation_limit, size_t max_accumulation_limit) {
470 size_t current_runs = number_of_sums;
471 size_t offset = 0;
472 while (current_runs != 0) {
473 static constexpr size_t DISPATCH_SIZE = 2048U;
474 size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE);
475 current_runs -= runs_to_do;
476 compute_pass_descriptor_queue.Acquire();
477 compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64));
478 compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64));
479 compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64));
480 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
481 size_t used_offset = offset;
482 offset += runs_to_do;
483
484 scheduler.RequestOutsideRenderPassOperationContext();
485 scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
486 runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
487 static constexpr VkMemoryBarrier read_barrier{
488 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
489 .pNext = nullptr,
490 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
491 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
492 };
493 static constexpr VkMemoryBarrier write_barrier{
494 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
495 .pNext = nullptr,
496 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
497 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
498 VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
499 VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
500 VK_ACCESS_UNIFORM_READ_BIT |
501 VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
502 };
503 const QueriesPrefixScanPushConstants uniforms{
504 .min_accumulation_base = static_cast<u32>(min_accumulation_limit),
505 .max_accumulation_base = static_cast<u32>(max_accumulation_limit),
506 .accumulation_limit = static_cast<u32>(runs_to_do - 1),
507 .buffer_offset = static_cast<u32>(used_offset),
508 };
509 const VkDescriptorSet set = descriptor_allocator.Commit();
510 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
511
512 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
513 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
514 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
515 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
516 cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
517 cmdbuf.Dispatch(1, 1, 1);
518 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
519 VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
520 write_barrier);
521 });
522 }
523}
524
305ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, 525ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
306 DescriptorPool& descriptor_pool_, 526 DescriptorPool& descriptor_pool_,
307 StagingBufferPool& staging_buffer_pool_, 527 StagingBufferPool& staging_buffer_pool_,
@@ -413,4 +633,100 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
413 scheduler.Finish(); 633 scheduler.Finish();
414} 634}
415 635
636MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
637 DescriptorPool& descriptor_pool_,
638 StagingBufferPool& staging_buffer_pool_,
639 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
640 : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS,
641 MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {},
642 CONVERT_NON_MSAA_TO_MSAA_COMP_SPV),
643 scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
644 compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {
645 const auto make_msaa_pipeline = [this](size_t i, std::span<const u32> code) {
646 modules[i] = device.GetLogical().CreateShaderModule({
647 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
648 .pNext = nullptr,
649 .flags = 0,
650 .codeSize = static_cast<u32>(code.size_bytes()),
651 .pCode = code.data(),
652 });
653 pipelines[i] = device.GetLogical().CreateComputePipeline({
654 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
655 .pNext = nullptr,
656 .flags = 0,
657 .stage{
658 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
659 .pNext = nullptr,
660 .flags = 0,
661 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
662 .module = *modules[i],
663 .pName = "main",
664 .pSpecializationInfo = nullptr,
665 },
666 .layout = *layout,
667 .basePipelineHandle = nullptr,
668 .basePipelineIndex = 0,
669 });
670 };
671 make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV);
672 make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV);
673}
674
675MSAACopyPass::~MSAACopyPass() = default;
676
677void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
678 std::span<const VideoCommon::ImageCopy> copies,
679 bool msaa_to_non_msaa) {
680 const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0];
681 scheduler.RequestOutsideRenderPassOperationContext();
682 for (const VideoCommon::ImageCopy& copy : copies) {
683 ASSERT(copy.src_subresource.base_layer == 0);
684 ASSERT(copy.src_subresource.num_layers == 1);
685 ASSERT(copy.dst_subresource.base_layer == 0);
686 ASSERT(copy.dst_subresource.num_layers == 1);
687
688 compute_pass_descriptor_queue.Acquire();
689 compute_pass_descriptor_queue.AddImage(
690 src_image.StorageImageView(copy.src_subresource.base_level));
691 compute_pass_descriptor_queue.AddImage(
692 dst_image.StorageImageView(copy.dst_subresource.base_level));
693 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
694
695 const Common::Vec3<u32> num_dispatches = {
696 Common::DivCeil(copy.extent.width, 8U),
697 Common::DivCeil(copy.extent.height, 8U),
698 copy.extent.depth,
699 };
700
701 scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches,
702 descriptor_data](vk::CommandBuffer cmdbuf) {
703 const VkDescriptorSet set = descriptor_allocator.Commit();
704 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
705 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline);
706 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
707 cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z);
708 const VkImageMemoryBarrier write_barrier{
709 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
710 .pNext = nullptr,
711 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
712 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
713 .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
714 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
715 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
716 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
717 .image = dst,
718 .subresourceRange{
719 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
720 .baseMipLevel = 0,
721 .levelCount = VK_REMAINING_MIP_LEVELS,
722 .baseArrayLayer = 0,
723 .layerCount = VK_REMAINING_ARRAY_LAYERS,
724 },
725 };
726 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
727 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
728 });
729 }
730}
731
416} // namespace Vulkan 732} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index dd3927376..7b8f938c1 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <optional>
6#include <span> 7#include <span>
7#include <utility> 8#include <utility>
8 9
@@ -10,6 +11,7 @@
10#include "video_core/engines/maxwell_3d.h" 11#include "video_core/engines/maxwell_3d.h"
11#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 12#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
12#include "video_core/renderer_vulkan/vk_update_descriptor.h" 13#include "video_core/renderer_vulkan/vk_update_descriptor.h"
14#include "video_core/texture_cache/types.h"
13#include "video_core/vulkan_common/vulkan_memory_allocator.h" 15#include "video_core/vulkan_common/vulkan_memory_allocator.h"
14#include "video_core/vulkan_common/vulkan_wrapper.h" 16#include "video_core/vulkan_common/vulkan_wrapper.h"
15 17
@@ -31,7 +33,8 @@ public:
31 vk::Span<VkDescriptorSetLayoutBinding> bindings, 33 vk::Span<VkDescriptorSetLayoutBinding> bindings,
32 vk::Span<VkDescriptorUpdateTemplateEntry> templates, 34 vk::Span<VkDescriptorUpdateTemplateEntry> templates,
33 const DescriptorBankInfo& bank_info, 35 const DescriptorBankInfo& bank_info,
34 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code); 36 vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
37 std::optional<u32> optional_subgroup_size = std::nullopt);
35 ~ComputePass(); 38 ~ComputePass();
36 39
37protected: 40protected:
@@ -82,6 +85,33 @@ private:
82 ComputePassDescriptorQueue& compute_pass_descriptor_queue; 85 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
83}; 86};
84 87
88class ConditionalRenderingResolvePass final : public ComputePass {
89public:
90 explicit ConditionalRenderingResolvePass(
91 const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
92 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
93
94 void Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, u32 src_offset, bool compare_to_zero);
95
96private:
97 Scheduler& scheduler;
98 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
99};
100
101class QueriesPrefixScanPass final : public ComputePass {
102public:
103 explicit QueriesPrefixScanPass(const Device& device_, Scheduler& scheduler_,
104 DescriptorPool& descriptor_pool_,
105 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
106
107 void Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, VkBuffer src_buffer,
108 size_t number_of_sums, size_t min_accumulation_limit, size_t max_accumulation_limit);
109
110private:
111 Scheduler& scheduler;
112 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
113};
114
85class ASTCDecoderPass final : public ComputePass { 115class ASTCDecoderPass final : public ComputePass {
86public: 116public:
87 explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, 117 explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
@@ -101,4 +131,22 @@ private:
101 MemoryAllocator& memory_allocator; 131 MemoryAllocator& memory_allocator;
102}; 132};
103 133
134class MSAACopyPass final : public ComputePass {
135public:
136 explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_,
137 DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
138 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
139 ~MSAACopyPass();
140
141 void CopyImage(Image& dst_image, Image& src_image,
142 std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
143
144private:
145 Scheduler& scheduler;
146 StagingBufferPool& staging_buffer_pool;
147 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
148 std::array<vk::ShaderModule, 2> modules;
149 std::array<vk::Pipeline, 2> pipelines;
150};
151
104} // namespace Vulkan 152} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 145359d4e..336573574 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -7,6 +7,7 @@
7 7
8#include "video_core/fence_manager.h" 8#include "video_core/fence_manager.h"
9#include "video_core/renderer_vulkan/vk_buffer_cache.h" 9#include "video_core/renderer_vulkan/vk_buffer_cache.h"
10#include "video_core/renderer_vulkan/vk_query_cache.h"
10#include "video_core/renderer_vulkan/vk_texture_cache.h" 11#include "video_core/renderer_vulkan/vk_texture_cache.h"
11 12
12namespace Core { 13namespace Core {
@@ -20,7 +21,6 @@ class RasterizerInterface;
20namespace Vulkan { 21namespace Vulkan {
21 22
22class Device; 23class Device;
23class QueryCache;
24class Scheduler; 24class Scheduler;
25 25
26class InnerFence : public VideoCommon::FenceBase { 26class InnerFence : public VideoCommon::FenceBase {
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 4f83a88e1..a1ec1a100 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -294,10 +294,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
294 texture_cache{texture_cache_}, shader_notify{shader_notify_}, 294 texture_cache{texture_cache_}, shader_notify{shader_notify_},
295 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, 295 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
296 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, 296 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
297 workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY 297#ifdef ANDROID
298 ? 1 298 workers(1, "VkPipelineBuilder"),
299 : (std::max(std::thread::hardware_concurrency(), 2U) - 1), 299#else
300 "VkPipelineBuilder"), 300 workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
301#endif
301 serialization_thread(1, "VkPipelineSerialization") { 302 serialization_thread(1, "VkPipelineSerialization") {
302 const auto& float_control{device.FloatControlProperties()}; 303 const auto& float_control{device.FloatControlProperties()};
303 const VkDriverId driver_id{device.GetDriverID()}; 304 const VkDriverId driver_id{device.GetDriverID()};
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 29e0b797b..17b2587ad 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1,139 +1,1554 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4#include <algorithm>
5#include <cstddef> 4#include <cstddef>
5#include <limits>
6#include <map>
7#include <memory>
8#include <span>
9#include <type_traits>
10#include <unordered_map>
6#include <utility> 11#include <utility>
7#include <vector> 12#include <vector>
8 13
14#include "common/bit_util.h"
15#include "common/common_types.h"
16#include "core/memory.h"
17#include "video_core/engines/draw_manager.h"
18#include "video_core/query_cache/query_cache.h"
19#include "video_core/renderer_vulkan/vk_buffer_cache.h"
20#include "video_core/renderer_vulkan/vk_compute_pass.h"
9#include "video_core/renderer_vulkan/vk_query_cache.h" 21#include "video_core/renderer_vulkan/vk_query_cache.h"
10#include "video_core/renderer_vulkan/vk_resource_pool.h" 22#include "video_core/renderer_vulkan/vk_resource_pool.h"
11#include "video_core/renderer_vulkan/vk_scheduler.h" 23#include "video_core/renderer_vulkan/vk_scheduler.h"
24#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
25#include "video_core/renderer_vulkan/vk_update_descriptor.h"
12#include "video_core/vulkan_common/vulkan_device.h" 26#include "video_core/vulkan_common/vulkan_device.h"
27#include "video_core/vulkan_common/vulkan_memory_allocator.h"
13#include "video_core/vulkan_common/vulkan_wrapper.h" 28#include "video_core/vulkan_common/vulkan_wrapper.h"
14 29
15namespace Vulkan { 30namespace Vulkan {
16 31
17using VideoCore::QueryType; 32using Tegra::Engines::Maxwell3D;
33using VideoCommon::QueryType;
18 34
19namespace { 35namespace {
36class SamplesQueryBank : public VideoCommon::BankBase {
37public:
38 static constexpr size_t BANK_SIZE = 256;
39 static constexpr size_t QUERY_SIZE = 8;
40 explicit SamplesQueryBank(const Device& device_, size_t index_)
41 : BankBase(BANK_SIZE), device{device_}, index{index_} {
42 const auto& dev = device.GetLogical();
43 query_pool = dev.CreateQueryPool({
44 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
45 .pNext = nullptr,
46 .flags = 0,
47 .queryType = VK_QUERY_TYPE_OCCLUSION,
48 .queryCount = BANK_SIZE,
49 .pipelineStatistics = 0,
50 });
51 Reset();
52 }
20 53
21constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION}; 54 ~SamplesQueryBank() = default;
22 55
23constexpr VkQueryType GetTarget(QueryType type) { 56 void Reset() override {
24 return QUERY_TARGETS[static_cast<std::size_t>(type)]; 57 ASSERT(references == 0);
25} 58 VideoCommon::BankBase::Reset();
59 const auto& dev = device.GetLogical();
60 dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
61 host_results.fill(0ULL);
62 next_bank = 0;
63 }
64
65 void Sync(size_t start, size_t size) {
66 const auto& dev = device.GetLogical();
67 const VkResult query_result = dev.GetQueryResults(
68 *query_pool, static_cast<u32>(start), static_cast<u32>(size), sizeof(u64) * size,
69 &host_results[start], sizeof(u64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
70 switch (query_result) {
71 case VK_SUCCESS:
72 return;
73 case VK_ERROR_DEVICE_LOST:
74 device.ReportLoss();
75 [[fallthrough]];
76 default:
77 throw vk::Exception(query_result);
78 }
79 }
80
81 VkQueryPool GetInnerPool() {
82 return *query_pool;
83 }
84
85 size_t GetIndex() const {
86 return index;
87 }
88
89 const std::array<u64, BANK_SIZE>& GetResults() const {
90 return host_results;
91 }
92
93 size_t next_bank;
94
95private:
96 const Device& device;
97 const size_t index;
98 vk::QueryPool query_pool;
99 std::array<u64, BANK_SIZE> host_results;
100};
101
102using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
103
104struct HostSyncValues {
105 VAddr address;
106 size_t size;
107 size_t offset;
108
109 static constexpr bool GeneratesBaseBuffer = false;
110};
111
112class SamplesStreamer : public BaseStreamer {
113public:
114 explicit SamplesStreamer(size_t id_, QueryCacheRuntime& runtime_,
115 VideoCore::RasterizerInterface* rasterizer_, const Device& device_,
116 Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
117 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
118 DescriptorPool& descriptor_pool)
119 : BaseStreamer(id_), runtime{runtime_}, rasterizer{rasterizer_}, device{device_},
120 scheduler{scheduler_}, memory_allocator{memory_allocator_} {
121 current_bank = nullptr;
122 current_query = nullptr;
123 ammend_value = 0;
124 acumulation_value = 0;
125 queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>(
126 device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
127
128 const VkBufferCreateInfo buffer_ci = {
129 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
130 .pNext = nullptr,
131 .flags = 0,
132 .size = 8,
133 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
134 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
135 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
136 .queueFamilyIndexCount = 0,
137 .pQueueFamilyIndices = nullptr,
138 };
139 accumulation_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
140 scheduler.RequestOutsideRenderPassOperationContext();
141 scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
142 cmdbuf.FillBuffer(buffer, 0, 8, 0);
143 });
144 }
145
146 ~SamplesStreamer() = default;
147
148 void StartCounter() override {
149 if (has_started) {
150 return;
151 }
152 ReserveHostQuery();
153 scheduler.Record([query_pool = current_query_pool,
154 query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
155 const bool use_precise = Settings::IsGPULevelHigh();
156 cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index),
157 use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
158 });
159 has_started = true;
160 }
161
162 void PauseCounter() override {
163 if (!has_started) {
164 return;
165 }
166 scheduler.Record([query_pool = current_query_pool,
167 query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
168 cmdbuf.EndQuery(query_pool, static_cast<u32>(query_index));
169 });
170 has_started = false;
171 }
172
173 void ResetCounter() override {
174 if (has_started) {
175 PauseCounter();
176 }
177 AbandonCurrentQuery();
178 std::function<void()> func([this, counts = pending_flush_queries.size()] {
179 ammend_value = 0;
180 acumulation_value = 0;
181 });
182 rasterizer->SyncOperation(std::move(func));
183 accumulation_since_last_sync = false;
184 first_accumulation_checkpoint = std::min(first_accumulation_checkpoint, num_slots_used);
185 last_accumulation_checkpoint = std::max(last_accumulation_checkpoint, num_slots_used);
186 }
187
188 void CloseCounter() override {
189 PauseCounter();
190 }
26 191
27} // Anonymous namespace 192 bool HasPendingSync() const override {
193 return !pending_sync.empty();
194 }
195
196 void SyncWrites() override {
197 if (sync_values_stash.empty()) {
198 return;
199 }
28 200
29QueryPool::QueryPool(const Device& device_, Scheduler& scheduler, QueryType type_) 201 for (size_t i = 0; i < sync_values_stash.size(); i++) {
30 : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {} 202 runtime.template SyncValues<HostSyncValues>(sync_values_stash[i],
203 *buffers[resolve_buffers[i]]);
204 }
205
206 sync_values_stash.clear();
207 }
31 208
32QueryPool::~QueryPool() = default; 209 void PresyncWrites() override {
210 if (pending_sync.empty()) {
211 return;
212 }
213 PauseCounter();
214 sync_values_stash.clear();
215 sync_values_stash.emplace_back();
216 std::vector<HostSyncValues>* sync_values = &sync_values_stash.back();
217 sync_values->reserve(num_slots_used);
218 std::unordered_map<size_t, std::pair<size_t, size_t>> offsets;
219 resolve_buffers.clear();
220 size_t resolve_buffer_index = ObtainBuffer<true>(num_slots_used);
221 resolve_buffers.push_back(resolve_buffer_index);
222 size_t base_offset = 0;
33 223
34std::pair<VkQueryPool, u32> QueryPool::Commit() { 224 ApplyBanksWideOp<true>(pending_sync, [&](SamplesQueryBank* bank, size_t start,
35 std::size_t index; 225 size_t amount) {
36 do { 226 size_t bank_id = bank->GetIndex();
37 index = CommitResource(); 227 auto& resolve_buffer = buffers[resolve_buffer_index];
38 } while (usage[index]); 228 VkQueryPool query_pool = bank->GetInnerPool();
39 usage[index] = true; 229 scheduler.RequestOutsideRenderPassOperationContext();
230 scheduler.Record([start, amount, base_offset, query_pool,
231 buffer = *resolve_buffer](vk::CommandBuffer cmdbuf) {
232 const VkBufferMemoryBarrier copy_query_pool_barrier{
233 .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
234 .pNext = nullptr,
235 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
236 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
237 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
238 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
239 .buffer = buffer,
240 .offset = base_offset,
241 .size = amount * SamplesQueryBank::QUERY_SIZE,
242 };
243
244 cmdbuf.CopyQueryPoolResults(
245 query_pool, static_cast<u32>(start), static_cast<u32>(amount), buffer,
246 static_cast<u32>(base_offset), SamplesQueryBank::QUERY_SIZE,
247 VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_64_BIT);
248 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
249 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, copy_query_pool_barrier);
250 });
251 offsets[bank_id] = {start, base_offset};
252 base_offset += amount * SamplesQueryBank::QUERY_SIZE;
253 });
254
255 // Convert queries
256 bool has_multi_queries = false;
257 for (auto q : pending_sync) {
258 auto* query = GetQuery(q);
259 size_t sync_value_slot = 0;
260 if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
261 continue;
262 }
263 if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
264 continue;
265 }
266 if (accumulation_since_last_sync || query->size_slots > 1) {
267 if (!has_multi_queries) {
268 has_multi_queries = true;
269 sync_values_stash.emplace_back();
270 }
271 sync_value_slot = 1;
272 }
273 query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
274 auto loc_data = offsets[query->start_bank_id];
275 sync_values_stash[sync_value_slot].emplace_back(HostSyncValues{
276 .address = query->guest_address,
277 .size = SamplesQueryBank::QUERY_SIZE,
278 .offset =
279 loc_data.second + (query->start_slot - loc_data.first + query->size_slots - 1) *
280 SamplesQueryBank::QUERY_SIZE,
281 });
282 }
283
284 if (has_multi_queries) {
285 size_t intermediary_buffer_index = ObtainBuffer<false>(num_slots_used);
286 resolve_buffers.push_back(intermediary_buffer_index);
287 queries_prefix_scan_pass->Run(*accumulation_buffer, *buffers[intermediary_buffer_index],
288 *buffers[resolve_buffer_index], num_slots_used,
289 std::min(first_accumulation_checkpoint, num_slots_used),
290 last_accumulation_checkpoint);
291
292 } else {
293 scheduler.RequestOutsideRenderPassOperationContext();
294 scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
295 cmdbuf.FillBuffer(buffer, 0, 8, 0);
296 });
297 }
298
299 ReplicateCurrentQueryIfNeeded();
300 std::function<void()> func([this] { ammend_value = acumulation_value; });
301 rasterizer->SyncOperation(std::move(func));
302 AbandonCurrentQuery();
303 num_slots_used = 0;
304 first_accumulation_checkpoint = std::numeric_limits<size_t>::max();
305 last_accumulation_checkpoint = 0;
306 accumulation_since_last_sync = has_multi_queries;
307 pending_sync.clear();
308 }
40 309
41 return {*pools[index / GROW_STEP], static_cast<u32>(index % GROW_STEP)}; 310 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
311 [[maybe_unused]] std::optional<u32> subreport) override {
312 PauseCounter();
313 auto index = BuildQuery();
314 auto* new_query = GetQuery(index);
315 new_query->guest_address = address;
316 new_query->value = 0;
317 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
318 if (has_timestamp) {
319 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
320 }
321 if (!current_query) {
322 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
323 return index;
324 }
325 new_query->start_bank_id = current_query->start_bank_id;
326 new_query->size_banks = current_query->size_banks;
327 new_query->start_slot = current_query->start_slot;
328 new_query->size_slots = current_query->size_slots;
329 ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
330 bank->AddReference(amount);
331 });
332 pending_sync.push_back(index);
333 pending_flush_queries.push_back(index);
334 return index;
335 }
336
337 bool HasUnsyncedQueries() const override {
338 return !pending_flush_queries.empty();
339 }
340
341 void PushUnsyncedQueries() override {
342 PauseCounter();
343 current_bank->Close();
344 {
345 std::scoped_lock lk(flush_guard);
346 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
347 }
348 }
349
350 void PopUnsyncedQueries() override {
351 std::vector<size_t> current_flush_queries;
352 {
353 std::scoped_lock lk(flush_guard);
354 current_flush_queries = std::move(pending_flush_sets.front());
355 pending_flush_sets.pop_front();
356 }
357 ApplyBanksWideOp<false>(
358 current_flush_queries,
359 [](SamplesQueryBank* bank, size_t start, size_t amount) { bank->Sync(start, amount); });
360 for (auto q : current_flush_queries) {
361 auto* query = GetQuery(q);
362 u64 total = 0;
363 ApplyBankOp(query, [&total](SamplesQueryBank* bank, size_t start, size_t amount) {
364 const auto& results = bank->GetResults();
365 for (size_t i = 0; i < amount; i++) {
366 total += results[start + i];
367 }
368 });
369 query->value = total;
370 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
371 }
372 }
373
374private:
375 template <typename Func>
376 void ApplyBankOp(VideoCommon::HostQueryBase* query, Func&& func) {
377 size_t size_slots = query->size_slots;
378 if (size_slots == 0) {
379 return;
380 }
381 size_t bank_id = query->start_bank_id;
382 size_t banks_set = query->size_banks;
383 size_t start_slot = query->start_slot;
384 for (size_t i = 0; i < banks_set; i++) {
385 auto& the_bank = bank_pool.GetBank(bank_id);
386 size_t amount = std::min(the_bank.Size() - start_slot, size_slots);
387 func(&the_bank, start_slot, amount);
388 bank_id = the_bank.next_bank - 1;
389 start_slot = 0;
390 size_slots -= amount;
391 }
392 }
393
394 template <bool is_ordered, typename Func>
395 void ApplyBanksWideOp(std::vector<size_t>& queries, Func&& func) {
396 std::conditional_t<is_ordered, std::map<size_t, std::pair<size_t, size_t>>,
397 std::unordered_map<size_t, std::pair<size_t, size_t>>>
398 indexer;
399 for (auto q : queries) {
400 auto* query = GetQuery(q);
401 ApplyBankOp(query, [&indexer](SamplesQueryBank* bank, size_t start, size_t amount) {
402 auto id_ = bank->GetIndex();
403 auto pair = indexer.try_emplace(id_, std::numeric_limits<size_t>::max(),
404 std::numeric_limits<size_t>::min());
405 auto& current_pair = pair.first->second;
406 current_pair.first = std::min(current_pair.first, start);
407 current_pair.second = std::max(current_pair.second, amount + start);
408 });
409 }
410 for (auto& cont : indexer) {
411 func(&bank_pool.GetBank(cont.first), cont.second.first,
412 cont.second.second - cont.second.first);
413 }
414 }
415
416 void ReserveBank() {
417 current_bank_id =
418 bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) {
419 queue.emplace_back(device, index);
420 });
421 if (current_bank) {
422 current_bank->next_bank = current_bank_id + 1;
423 }
424 current_bank = &bank_pool.GetBank(current_bank_id);
425 current_query_pool = current_bank->GetInnerPool();
426 }
427
428 size_t ReserveBankSlot() {
429 if (!current_bank || current_bank->IsClosed()) {
430 ReserveBank();
431 }
432 auto [built, index] = current_bank->Reserve();
433 current_bank_slot = index;
434 return index;
435 }
436
437 void ReserveHostQuery() {
438 size_t new_slot = ReserveBankSlot();
439 current_bank->AddReference(1);
440 num_slots_used++;
441 if (current_query) {
442 size_t bank_id = current_query->start_bank_id;
443 size_t banks_set = current_query->size_banks - 1;
444 bool found = bank_id == current_bank_id;
445 while (!found && banks_set > 0) {
446 SamplesQueryBank& some_bank = bank_pool.GetBank(bank_id);
447 bank_id = some_bank.next_bank - 1;
448 found = bank_id == current_bank_id;
449 banks_set--;
450 }
451 if (!found) {
452 current_query->size_banks++;
453 }
454 current_query->size_slots++;
455 } else {
456 current_query_id = BuildQuery();
457 current_query = GetQuery(current_query_id);
458 current_query->start_bank_id = static_cast<u32>(current_bank_id);
459 current_query->size_banks = 1;
460 current_query->start_slot = new_slot;
461 current_query->size_slots = 1;
462 }
463 }
464
465 void Free(size_t query_id) override {
466 std::scoped_lock lk(guard);
467 auto* query = GetQuery(query_id);
468 ApplyBankOp(query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
469 bank->CloseReference(amount);
470 });
471 ReleaseQuery(query_id);
472 }
473
474 void AbandonCurrentQuery() {
475 if (!current_query) {
476 return;
477 }
478 Free(current_query_id);
479 current_query = nullptr;
480 current_query_id = 0;
481 }
482
483 void ReplicateCurrentQueryIfNeeded() {
484 if (pending_sync.empty()) {
485 return;
486 }
487 if (!current_query) {
488 return;
489 }
490 auto index = BuildQuery();
491 auto* new_query = GetQuery(index);
492 new_query->guest_address = 0;
493 new_query->value = 0;
494 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
495 new_query->start_bank_id = current_query->start_bank_id;
496 new_query->size_banks = current_query->size_banks;
497 new_query->start_slot = current_query->start_slot;
498 new_query->size_slots = current_query->size_slots;
499 ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
500 bank->AddReference(amount);
501 });
502 pending_flush_queries.push_back(index);
503 std::function<void()> func([this, index] {
504 auto* query = GetQuery(index);
505 query->value += GetAmmendValue();
506 SetAccumulationValue(query->value);
507 Free(index);
508 });
509 }
510
511 template <bool is_resolve>
512 size_t ObtainBuffer(size_t num_needed) {
513 const size_t log_2 = std::max<size_t>(11U, Common::Log2Ceil64(num_needed));
514 if constexpr (is_resolve) {
515 if (resolve_table[log_2] != 0) {
516 return resolve_table[log_2] - 1;
517 }
518 } else {
519 if (intermediary_table[log_2] != 0) {
520 return intermediary_table[log_2] - 1;
521 }
522 }
523 const VkBufferCreateInfo buffer_ci = {
524 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
525 .pNext = nullptr,
526 .flags = 0,
527 .size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2),
528 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
529 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
530 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
531 .queueFamilyIndexCount = 0,
532 .pQueueFamilyIndices = nullptr,
533 };
534 buffers.emplace_back(memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal));
535 if constexpr (is_resolve) {
536 resolve_table[log_2] = buffers.size();
537 } else {
538 intermediary_table[log_2] = buffers.size();
539 }
540 return buffers.size() - 1;
541 }
542
543 QueryCacheRuntime& runtime;
544 VideoCore::RasterizerInterface* rasterizer;
545 const Device& device;
546 Scheduler& scheduler;
547 const MemoryAllocator& memory_allocator;
548 VideoCommon::BankPool<SamplesQueryBank> bank_pool;
549 std::deque<vk::Buffer> buffers;
550 std::array<size_t, 32> resolve_table{};
551 std::array<size_t, 32> intermediary_table{};
552 vk::Buffer accumulation_buffer;
553 std::deque<std::vector<HostSyncValues>> sync_values_stash;
554 std::vector<size_t> resolve_buffers;
555
556 // syncing queue
557 std::vector<size_t> pending_sync;
558
559 // flush levels
560 std::vector<size_t> pending_flush_queries;
561 std::deque<std::vector<size_t>> pending_flush_sets;
562
563 // State Machine
564 size_t current_bank_slot;
565 size_t current_bank_id;
566 SamplesQueryBank* current_bank;
567 VkQueryPool current_query_pool;
568 size_t current_query_id;
569 size_t num_slots_used{};
570 size_t first_accumulation_checkpoint{};
571 size_t last_accumulation_checkpoint{};
572 bool accumulation_since_last_sync{};
573 VideoCommon::HostQueryBase* current_query;
574 bool has_started{};
575 std::mutex flush_guard;
576
577 std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
578};
579
580// Transform feedback queries
581class TFBQueryBank : public VideoCommon::BankBase {
582public:
583 static constexpr size_t BANK_SIZE = 1024;
584 static constexpr size_t QUERY_SIZE = 4;
585 explicit TFBQueryBank(Scheduler& scheduler_, const MemoryAllocator& memory_allocator,
586 size_t index_)
587 : BankBase(BANK_SIZE), scheduler{scheduler_}, index{index_} {
588 const VkBufferCreateInfo buffer_ci = {
589 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
590 .pNext = nullptr,
591 .flags = 0,
592 .size = QUERY_SIZE * BANK_SIZE,
593 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
594 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
595 .queueFamilyIndexCount = 0,
596 .pQueueFamilyIndices = nullptr,
597 };
598 buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
599 }
600
601 ~TFBQueryBank() = default;
602
603 void Reset() override {
604 ASSERT(references == 0);
605 VideoCommon::BankBase::Reset();
606 }
607
608 void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
609 scheduler.RequestOutsideRenderPassOperationContext();
610 scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
611 size](vk::CommandBuffer cmdbuf) {
612 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
613 .srcOffset = start * QUERY_SIZE,
614 .dstOffset = extra_offset,
615 .size = size * QUERY_SIZE,
616 }};
617 cmdbuf.CopyBuffer(*buffer, dst_buffer, copy);
618 });
619 }
620
621 size_t GetIndex() const {
622 return index;
623 }
624
625 VkBuffer GetBuffer() const {
626 return *buffer;
627 }
628
629private:
630 Scheduler& scheduler;
631 const size_t index;
632 vk::Buffer buffer;
633};
634
635class PrimitivesSucceededStreamer;
636
637class TFBCounterStreamer : public BaseStreamer {
638public:
639 explicit TFBCounterStreamer(size_t id_, QueryCacheRuntime& runtime_, const Device& device_,
640 Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
641 StagingBufferPool& staging_pool_)
642 : BaseStreamer(id_), runtime{runtime_}, device{device_}, scheduler{scheduler_},
643 memory_allocator{memory_allocator_}, staging_pool{staging_pool_} {
644 buffers_count = 0;
645 current_bank = nullptr;
646 counter_buffers.fill(VK_NULL_HANDLE);
647 offsets.fill(0);
648 last_queries.fill(0);
649 last_queries_stride.fill(1);
650 const VkBufferCreateInfo buffer_ci = {
651 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
652 .pNext = nullptr,
653 .flags = 0,
654 .size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS,
655 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
656 VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT,
657 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
658 .queueFamilyIndexCount = 0,
659 .pQueueFamilyIndices = nullptr,
660 };
661
662 counters_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
663 for (auto& c : counter_buffers) {
664 c = *counters_buffer;
665 }
666 size_t base_offset = 0;
667 for (auto& o : offsets) {
668 o = base_offset;
669 base_offset += TFBQueryBank::QUERY_SIZE;
670 }
671 }
672
673 ~TFBCounterStreamer() = default;
674
675 void StartCounter() override {
676 FlushBeginTFB();
677 has_started = true;
678 }
679
680 void PauseCounter() override {
681 CloseCounter();
682 }
683
684 void ResetCounter() override {
685 CloseCounter();
686 }
687
688 void CloseCounter() override {
689 if (has_flushed_end_pending) {
690 FlushEndTFB();
691 }
692 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
693 if (maxwell3d.regs.transform_feedback_enabled == 0) {
694 streams_mask = 0;
695 has_started = false;
696 }
697 });
698 }
699
700 bool HasPendingSync() const override {
701 return !pending_sync.empty();
702 }
703
704 void SyncWrites() override {
705 CloseCounter();
706 std::unordered_map<size_t, std::vector<HostSyncValues>> sync_values_stash;
707 for (auto q : pending_sync) {
708 auto* query = GetQuery(q);
709 if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
710 continue;
711 }
712 if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
713 continue;
714 }
715 query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
716 sync_values_stash.try_emplace(query->start_bank_id);
717 sync_values_stash[query->start_bank_id].emplace_back(HostSyncValues{
718 .address = query->guest_address,
719 .size = TFBQueryBank::QUERY_SIZE,
720 .offset = query->start_slot * TFBQueryBank::QUERY_SIZE,
721 });
722 }
723 for (auto& p : sync_values_stash) {
724 auto& bank = bank_pool.GetBank(p.first);
725 runtime.template SyncValues<HostSyncValues>(p.second, bank.GetBuffer());
726 }
727 pending_sync.clear();
728 }
729
730 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
731 std::optional<u32> subreport_) override {
732 auto index = BuildQuery();
733 auto* new_query = GetQuery(index);
734 new_query->guest_address = address;
735 new_query->value = 0;
736 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
737 if (has_timestamp) {
738 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
739 }
740 if (!subreport_) {
741 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
742 return index;
743 }
744 const size_t subreport = static_cast<size_t>(*subreport_);
745 last_queries[subreport] = address;
746 if ((streams_mask & (1ULL << subreport)) == 0) {
747 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
748 return index;
749 }
750 CloseCounter();
751 auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport);
752 new_query->start_bank_id = static_cast<u32>(bank_slot);
753 new_query->size_banks = 1;
754 new_query->start_slot = static_cast<u32>(data_slot);
755 new_query->size_slots = 1;
756 pending_sync.push_back(index);
757 pending_flush_queries.push_back(index);
758 return index;
759 }
760
761 std::optional<std::pair<VAddr, size_t>> GetLastQueryStream(size_t stream) {
762 if (last_queries[stream] != 0) {
763 std::pair<VAddr, size_t> result(last_queries[stream], last_queries_stride[stream]);
764 return result;
765 }
766 return std::nullopt;
767 }
768
769 Maxwell3D::Regs::PrimitiveTopology GetOutputTopology() const {
770 return out_topology;
771 }
772
773 bool HasUnsyncedQueries() const override {
774 return !pending_flush_queries.empty();
775 }
776
777 void PushUnsyncedQueries() override {
778 CloseCounter();
779 auto staging_ref = staging_pool.Request(
780 pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
781 size_t offset_base = staging_ref.offset;
782 for (auto q : pending_flush_queries) {
783 auto* query = GetQuery(q);
784 auto& bank = bank_pool.GetBank(query->start_bank_id);
785 bank.Sync(staging_ref, offset_base, query->start_slot, 1);
786 offset_base += TFBQueryBank::QUERY_SIZE;
787 bank.CloseReference();
788 }
789 static constexpr VkMemoryBarrier WRITE_BARRIER{
790 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
791 .pNext = nullptr,
792 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
793 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
794 };
795 scheduler.RequestOutsideRenderPassOperationContext();
796 scheduler.Record([](vk::CommandBuffer cmdbuf) {
797 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
798 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
799 });
800
801 std::scoped_lock lk(flush_guard);
802 for (auto& str : free_queue) {
803 staging_pool.FreeDeferred(str);
804 }
805 free_queue.clear();
806 download_buffers.emplace_back(staging_ref);
807 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
808 }
809
810 void PopUnsyncedQueries() override {
811 StagingBufferRef staging_ref;
812 std::vector<size_t> flushed_queries;
813 {
814 std::scoped_lock lk(flush_guard);
815 staging_ref = download_buffers.front();
816 flushed_queries = std::move(pending_flush_sets.front());
817 download_buffers.pop_front();
818 pending_flush_sets.pop_front();
819 }
820
821 size_t offset_base = staging_ref.offset;
822 for (auto q : flushed_queries) {
823 auto* query = GetQuery(q);
824 u32 result = 0;
825 std::memcpy(&result, staging_ref.mapped_span.data() + offset_base, sizeof(u32));
826 query->value = static_cast<u64>(result);
827 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
828 offset_base += TFBQueryBank::QUERY_SIZE;
829 }
830
831 {
832 std::scoped_lock lk(flush_guard);
833 free_queue.emplace_back(staging_ref);
834 }
835 }
836
837private:
838 void FlushBeginTFB() {
839 if (has_flushed_end_pending) [[unlikely]] {
840 return;
841 }
842 has_flushed_end_pending = true;
843 if (!has_started || buffers_count == 0) {
844 scheduler.Record([](vk::CommandBuffer cmdbuf) {
845 cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
846 });
847 UpdateBuffers();
848 return;
849 }
850 scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
851 cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
852 });
853 UpdateBuffers();
854 }
855
856 void FlushEndTFB() {
857 if (!has_flushed_end_pending) [[unlikely]] {
858 UNREACHABLE();
859 return;
860 }
861 has_flushed_end_pending = false;
862
863 if (buffers_count == 0) {
864 scheduler.Record([](vk::CommandBuffer cmdbuf) {
865 cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr);
866 });
867 } else {
868 scheduler.Record([this,
869 total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
870 cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
871 });
872 }
873 }
874
875 void UpdateBuffers() {
876 last_queries.fill(0);
877 last_queries_stride.fill(1);
878 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
879 buffers_count = 0;
880 out_topology = maxwell3d.draw_manager->GetDrawState().topology;
881 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
882 const auto& tf = maxwell3d.regs.transform_feedback;
883 if (tf.buffers[i].enable == 0) {
884 continue;
885 }
886 const size_t stream = tf.controls[i].stream;
887 last_queries_stride[stream] = tf.controls[i].stride;
888 streams_mask |= 1ULL << stream;
889 buffers_count = std::max<size_t>(buffers_count, stream + 1);
890 }
891 });
892 }
893
894 std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) {
895 if (current_bank == nullptr || current_bank->IsClosed()) {
896 current_bank_id =
897 bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) {
898 queue.emplace_back(scheduler, memory_allocator, index);
899 });
900 current_bank = &bank_pool.GetBank(current_bank_id);
901 }
902 auto [dont_care, other] = current_bank->Reserve();
903 const size_t slot = other; // workaround to compile bug.
904 current_bank->AddReference();
905
906 static constexpr VkMemoryBarrier READ_BARRIER{
907 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
908 .pNext = nullptr,
909 .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
910 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
911 };
912 static constexpr VkMemoryBarrier WRITE_BARRIER{
913 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
914 .pNext = nullptr,
915 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
916 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
917 };
918 scheduler.RequestOutsideRenderPassOperationContext();
919 scheduler.Record([dst_buffer = current_bank->GetBuffer(),
920 src_buffer = counter_buffers[stream], src_offset = offsets[stream],
921 slot](vk::CommandBuffer cmdbuf) {
922 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
923 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
924 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
925 .srcOffset = src_offset,
926 .dstOffset = slot * TFBQueryBank::QUERY_SIZE,
927 .size = TFBQueryBank::QUERY_SIZE,
928 }};
929 cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
930 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
931 0, WRITE_BARRIER);
932 });
933 return {current_bank_id, slot};
934 }
935
936 friend class PrimitivesSucceededStreamer;
937
938 static constexpr size_t NUM_STREAMS = 4;
939
940 QueryCacheRuntime& runtime;
941 const Device& device;
942 Scheduler& scheduler;
943 const MemoryAllocator& memory_allocator;
944 StagingBufferPool& staging_pool;
945 VideoCommon::BankPool<TFBQueryBank> bank_pool;
946 size_t current_bank_id;
947 TFBQueryBank* current_bank;
948 vk::Buffer counters_buffer;
949
950 // syncing queue
951 std::vector<size_t> pending_sync;
952
953 // flush levels
954 std::vector<size_t> pending_flush_queries;
955 std::deque<StagingBufferRef> download_buffers;
956 std::deque<std::vector<size_t>> pending_flush_sets;
957 std::vector<StagingBufferRef> free_queue;
958 std::mutex flush_guard;
959
960 // state machine
961 bool has_started{};
962 bool has_flushed_end_pending{};
963 size_t buffers_count{};
964 std::array<VkBuffer, NUM_STREAMS> counter_buffers{};
965 std::array<VkDeviceSize, NUM_STREAMS> offsets{};
966 std::array<VAddr, NUM_STREAMS> last_queries;
967 std::array<size_t, NUM_STREAMS> last_queries_stride;
968 Maxwell3D::Regs::PrimitiveTopology out_topology;
969 u64 streams_mask;
970};
971
972class PrimitivesQueryBase : public VideoCommon::QueryBase {
973public:
974 // Default constructor
975 PrimitivesQueryBase()
976 : VideoCommon::QueryBase(0, VideoCommon::QueryFlagBits::IsHostManaged, 0) {}
977
978 // Parameterized constructor
979 PrimitivesQueryBase(bool has_timestamp, VAddr address)
980 : VideoCommon::QueryBase(address, VideoCommon::QueryFlagBits::IsHostManaged, 0) {
981 if (has_timestamp) {
982 flags |= VideoCommon::QueryFlagBits::HasTimestamp;
983 }
984 }
985
986 u64 stride{};
987 VAddr dependant_address{};
988 Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
989 size_t dependant_index{};
990 bool dependant_manage{};
991};
992
993class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> {
994public:
995 explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_,
996 TFBCounterStreamer& tfb_streamer_,
997 Core::Memory::Memory& cpu_memory_)
998 : VideoCommon::SimpleStreamer<PrimitivesQueryBase>(id_), runtime{runtime_},
999 tfb_streamer{tfb_streamer_}, cpu_memory{cpu_memory_} {
1000 MakeDependent(&tfb_streamer);
1001 }
1002
1003 ~PrimitivesSucceededStreamer() = default;
1004
1005 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
1006 std::optional<u32> subreport_) override {
1007 auto index = BuildQuery();
1008 auto* new_query = GetQuery(index);
1009 new_query->guest_address = address;
1010 new_query->value = 0;
1011 if (has_timestamp) {
1012 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
1013 }
1014 if (!subreport_) {
1015 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1016 return index;
1017 }
1018 const size_t subreport = static_cast<size_t>(*subreport_);
1019 auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
1020 bool must_manage_dependance = false;
1021 new_query->topology = tfb_streamer.GetOutputTopology();
1022 if (dependant_address_opt) {
1023 auto [dep_address, stride] = *dependant_address_opt;
1024 new_query->dependant_address = dep_address;
1025 new_query->stride = stride;
1026 } else {
1027 new_query->dependant_index =
1028 tfb_streamer.WriteCounter(address, has_timestamp, value, subreport_);
1029 auto* dependant_query = tfb_streamer.GetQuery(new_query->dependant_index);
1030 dependant_query->flags |= VideoCommon::QueryFlagBits::IsInvalidated;
1031 must_manage_dependance = true;
1032 if (True(dependant_query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1033 new_query->value = 0;
1034 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1035 if (must_manage_dependance) {
1036 tfb_streamer.Free(new_query->dependant_index);
1037 }
1038 return index;
1039 }
1040 new_query->stride = 1;
1041 runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
1042 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
1043 const auto& tf = maxwell3d.regs.transform_feedback;
1044 if (tf.buffers[i].enable == 0) {
1045 continue;
1046 }
1047 if (tf.controls[i].stream != subreport) {
1048 continue;
1049 }
1050 new_query->stride = tf.controls[i].stride;
1051 break;
1052 }
1053 });
1054 }
1055
1056 new_query->dependant_manage = must_manage_dependance;
1057 pending_flush_queries.push_back(index);
1058 return index;
1059 }
1060
1061 bool HasUnsyncedQueries() const override {
1062 return !pending_flush_queries.empty();
1063 }
1064
1065 void PushUnsyncedQueries() override {
1066 std::scoped_lock lk(flush_guard);
1067 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
1068 pending_flush_queries.clear();
1069 }
1070
1071 void PopUnsyncedQueries() override {
1072 std::vector<size_t> flushed_queries;
1073 {
1074 std::scoped_lock lk(flush_guard);
1075 flushed_queries = std::move(pending_flush_sets.front());
1076 pending_flush_sets.pop_front();
1077 }
1078
1079 for (auto q : flushed_queries) {
1080 auto* query = GetQuery(q);
1081 if (True(query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1082 continue;
1083 }
1084
1085 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1086 u64 num_vertices = 0;
1087 if (query->dependant_manage) {
1088 auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index);
1089 num_vertices = dependant_query->value / query->stride;
1090 tfb_streamer.Free(query->dependant_index);
1091 } else {
1092 u8* pointer = cpu_memory.GetPointer(query->dependant_address);
1093 u32 result;
1094 std::memcpy(&result, pointer, sizeof(u32));
1095 num_vertices = static_cast<u64>(result) / query->stride;
1096 }
1097 query->value = [&]() -> u64 {
1098 switch (query->topology) {
1099 case Maxwell3D::Regs::PrimitiveTopology::Points:
1100 return num_vertices;
1101 case Maxwell3D::Regs::PrimitiveTopology::Lines:
1102 return num_vertices / 2;
1103 case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
1104 return (num_vertices / 2) + 1;
1105 case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
1106 return num_vertices - 1;
1107 case Maxwell3D::Regs::PrimitiveTopology::Patches:
1108 case Maxwell3D::Regs::PrimitiveTopology::Triangles:
1109 case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
1110 return num_vertices / 3;
1111 case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
1112 case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
1113 case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
1114 return num_vertices - 2;
1115 case Maxwell3D::Regs::PrimitiveTopology::Quads:
1116 return num_vertices / 4;
1117 case Maxwell3D::Regs::PrimitiveTopology::Polygon:
1118 return 1U;
1119 default:
1120 return num_vertices;
1121 }
1122 }();
1123 }
1124 }
1125
1126private:
1127 QueryCacheRuntime& runtime;
1128 TFBCounterStreamer& tfb_streamer;
1129 Core::Memory::Memory& cpu_memory;
1130
1131 // syncing queue
1132 std::vector<size_t> pending_sync;
1133
1134 // flush levels
1135 std::vector<size_t> pending_flush_queries;
1136 std::deque<std::vector<size_t>> pending_flush_sets;
1137 std::mutex flush_guard;
1138};
1139
1140} // namespace
1141
1142struct QueryCacheRuntimeImpl {
1143 QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_,
1144 Core::Memory::Memory& cpu_memory_, Vulkan::BufferCache& buffer_cache_,
1145 const Device& device_, const MemoryAllocator& memory_allocator_,
1146 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1147 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1148 DescriptorPool& descriptor_pool)
1149 : rasterizer{rasterizer_}, cpu_memory{cpu_memory_},
1150 buffer_cache{buffer_cache_}, device{device_},
1151 memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_},
1152 guest_streamer(0, runtime),
1153 sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
1154 device, scheduler, memory_allocator, compute_pass_descriptor_queue,
1155 descriptor_pool),
1156 tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
1157 scheduler, memory_allocator, staging_pool),
1158 primitives_succeeded_streamer(
1159 static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
1160 cpu_memory_),
1161 primitives_needed_minus_suceeded_streamer(
1162 static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
1163 hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
1164
1165 hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
1166 hcr_setup.pNext = nullptr;
1167 hcr_setup.flags = 0;
1168
1169 conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
1170 device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
1171
1172 const VkBufferCreateInfo buffer_ci = {
1173 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
1174 .pNext = nullptr,
1175 .flags = 0,
1176 .size = sizeof(u32),
1177 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
1178 VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
1179 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
1180 .queueFamilyIndexCount = 0,
1181 .pQueueFamilyIndices = nullptr,
1182 };
1183 hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
1184 }
1185
1186 VideoCore::RasterizerInterface* rasterizer;
1187 Core::Memory::Memory& cpu_memory;
1188 Vulkan::BufferCache& buffer_cache;
1189
1190 const Device& device;
1191 const MemoryAllocator& memory_allocator;
1192 Scheduler& scheduler;
1193 StagingBufferPool& staging_pool;
1194
1195 // Streamers
1196 VideoCommon::GuestStreamer<QueryCacheParams> guest_streamer;
1197 SamplesStreamer sample_streamer;
1198 TFBCounterStreamer tfb_streamer;
1199 PrimitivesSucceededStreamer primitives_succeeded_streamer;
1200 VideoCommon::StubStreamer<QueryCacheParams> primitives_needed_minus_suceeded_streamer;
1201
1202 std::vector<std::pair<VAddr, VAddr>> little_cache;
1203 std::vector<std::pair<VkBuffer, VkDeviceSize>> buffers_to_upload_to;
1204 std::vector<size_t> redirect_cache;
1205 std::vector<std::vector<VkBufferCopy>> copies_setup;
1206
1207 // Host conditional rendering data
1208 std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass;
1209 vk::Buffer hcr_resolve_buffer;
1210 VkConditionalRenderingBeginInfoEXT hcr_setup;
1211 VkBuffer hcr_buffer;
1212 size_t hcr_offset;
1213 bool hcr_is_set;
1214 bool is_hcr_running;
1215
1216 // maxwell3d
1217 Maxwell3D* maxwell3d;
1218};
1219
1220QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
1221 Core::Memory::Memory& cpu_memory_,
1222 Vulkan::BufferCache& buffer_cache_, const Device& device_,
1223 const MemoryAllocator& memory_allocator_,
1224 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1225 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1226 DescriptorPool& descriptor_pool) {
1227 impl = std::make_unique<QueryCacheRuntimeImpl>(
1228 *this, rasterizer, cpu_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
1229 staging_pool_, compute_pass_descriptor_queue, descriptor_pool);
42} 1230}
43 1231
44void QueryPool::Allocate(std::size_t begin, std::size_t end) { 1232void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) {
45 usage.resize(end); 1233 impl->maxwell3d = maxwell3d;
1234}
46 1235
47 pools.push_back(device.GetLogical().CreateQueryPool({ 1236template <typename Func>
48 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, 1237void QueryCacheRuntime::View3DRegs(Func&& func) {
49 .pNext = nullptr, 1238 if (impl->maxwell3d) {
50 .flags = 0, 1239 func(*impl->maxwell3d);
51 .queryType = GetTarget(type), 1240 }
52 .queryCount = static_cast<u32>(end - begin), 1241}
53 .pipelineStatistics = 0, 1242
54 })); 1243void QueryCacheRuntime::EndHostConditionalRendering() {
1244 PauseHostConditionalRendering();
1245 impl->hcr_is_set = false;
1246 impl->is_hcr_running = false;
1247 impl->hcr_buffer = nullptr;
1248 impl->hcr_offset = 0;
1249}
1250
1251void QueryCacheRuntime::PauseHostConditionalRendering() {
1252 if (!impl->hcr_is_set) {
1253 return;
1254 }
1255 if (impl->is_hcr_running) {
1256 impl->scheduler.Record(
1257 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndConditionalRenderingEXT(); });
1258 }
1259 impl->is_hcr_running = false;
55} 1260}
56 1261
57void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { 1262void QueryCacheRuntime::ResumeHostConditionalRendering() {
58 const auto it = 1263 if (!impl->hcr_is_set) {
59 std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) { 1264 return;
60 return query_pool == *pool; 1265 }
1266 if (!impl->is_hcr_running) {
1267 impl->scheduler.Record([hcr_setup = impl->hcr_setup](vk::CommandBuffer cmdbuf) {
1268 cmdbuf.BeginConditionalRenderingEXT(hcr_setup);
61 }); 1269 });
1270 }
1271 impl->is_hcr_running = true;
1272}
62 1273
63 if (it != std::end(pools)) { 1274void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object,
64 const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); 1275 bool is_equal) {
65 usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; 1276 {
1277 std::scoped_lock lk(impl->buffer_cache.mutex);
1278 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1279 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1280 const auto [buffer, offset] =
1281 impl->buffer_cache.ObtainCPUBuffer(object.address, 8, sync_info, post_op);
1282 impl->hcr_buffer = buffer->Handle();
1283 impl->hcr_offset = offset;
1284 }
1285 if (impl->hcr_is_set) {
1286 if (impl->hcr_setup.buffer == impl->hcr_buffer &&
1287 impl->hcr_setup.offset == impl->hcr_offset) {
1288 ResumeHostConditionalRendering();
1289 return;
1290 }
1291 PauseHostConditionalRendering();
66 } 1292 }
1293 impl->hcr_setup.buffer = impl->hcr_buffer;
1294 impl->hcr_setup.offset = impl->hcr_offset;
1295 impl->hcr_setup.flags = is_equal ? VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT : 0;
1296 impl->hcr_is_set = true;
1297 impl->is_hcr_running = false;
1298 ResumeHostConditionalRendering();
67} 1299}
68 1300
69QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, 1301void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal) {
70 Core::Memory::Memory& cpu_memory_, const Device& device_, 1302 VkBuffer to_resolve;
71 Scheduler& scheduler_) 1303 u32 to_resolve_offset;
72 : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_}, 1304 {
73 query_pools{ 1305 std::scoped_lock lk(impl->buffer_cache.mutex);
74 QueryPool{device_, scheduler_, QueryType::SamplesPassed}, 1306 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize;
75 } {} 1307 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
76 1308 const auto [buffer, offset] =
77QueryCache::~QueryCache() { 1309 impl->buffer_cache.ObtainCPUBuffer(address, 24, sync_info, post_op);
78 // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class 1310 to_resolve = buffer->Handle();
79 // destructor is called. The query cache should be redesigned to have a proper ownership model 1311 to_resolve_offset = static_cast<u32>(offset);
80 // instead of using shared pointers.
81 for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) {
82 auto& stream = Stream(static_cast<QueryType>(query_type));
83 stream.Update(false);
84 stream.Reset();
85 } 1312 }
1313 if (impl->is_hcr_running) {
1314 PauseHostConditionalRendering();
1315 }
1316 impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve,
1317 to_resolve_offset, false);
1318 impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
1319 impl->hcr_setup.offset = 0;
1320 impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
1321 impl->hcr_is_set = true;
1322 impl->is_hcr_running = false;
1323 ResumeHostConditionalRendering();
86} 1324}
87 1325
88std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) { 1326bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1,
89 return query_pools[static_cast<std::size_t>(type)].Commit(); 1327 [[maybe_unused]] bool qc_dirty) {
1328 if (!impl->device.IsExtConditionalRendering()) {
1329 return false;
1330 }
1331 HostConditionalRenderingCompareValueImpl(object_1, false);
1332 return true;
90} 1333}
91 1334
92void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) { 1335bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
93 query_pools[static_cast<std::size_t>(type)].Reserve(query); 1336 VideoCommon::LookupData object_2,
1337 bool qc_dirty, bool equal_check) {
1338 if (!impl->device.IsExtConditionalRendering()) {
1339 return false;
1340 }
1341
1342 const auto check_in_bc = [&](VAddr address) {
1343 return impl->buffer_cache.IsRegionGpuModified(address, 8);
1344 };
1345 const auto check_value = [&](VAddr address) {
1346 u8* ptr = impl->cpu_memory.GetPointer(address);
1347 u64 value{};
1348 std::memcpy(&value, ptr, sizeof(value));
1349 return value == 0;
1350 };
1351 std::array<VideoCommon::LookupData*, 2> objects{&object_1, &object_2};
1352 std::array<bool, 2> is_in_bc{};
1353 std::array<bool, 2> is_in_qc{};
1354 std::array<bool, 2> is_in_ac{};
1355 std::array<bool, 2> is_null{};
1356 {
1357 std::scoped_lock lk(impl->buffer_cache.mutex);
1358 for (size_t i = 0; i < 2; i++) {
1359 is_in_qc[i] = objects[i]->found_query != nullptr;
1360 is_in_bc[i] = !is_in_qc[i] && check_in_bc(objects[i]->address);
1361 is_in_ac[i] = is_in_qc[i] || is_in_bc[i];
1362 }
1363 }
1364
1365 if (!is_in_ac[0] && !is_in_ac[1]) {
1366 EndHostConditionalRendering();
1367 return false;
1368 }
1369
1370 if (!qc_dirty && !is_in_bc[0] && !is_in_bc[1]) {
1371 EndHostConditionalRendering();
1372 return false;
1373 }
1374
1375 const bool is_gpu_high = Settings::IsGPULevelHigh();
1376 if (!is_gpu_high && impl->device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
1377 return true;
1378 }
1379
1380 for (size_t i = 0; i < 2; i++) {
1381 is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
1382 }
1383
1384 for (size_t i = 0; i < 2; i++) {
1385 if (is_null[i]) {
1386 size_t j = (i + 1) % 2;
1387 HostConditionalRenderingCompareValueImpl(*objects[j], equal_check);
1388 return true;
1389 }
1390 }
1391
1392 if (!is_gpu_high) {
1393 return true;
1394 }
1395
1396 if (!is_in_bc[0] && !is_in_bc[1]) {
1397 // Both queries are in query cache, it's best to just flush.
1398 return true;
1399 }
1400 HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
1401 return true;
94} 1402}
95 1403
96HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, 1404QueryCacheRuntime::~QueryCacheRuntime() = default;
97 QueryType type_) 1405
98 : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, 1406VideoCommon::StreamerInterface* QueryCacheRuntime::GetStreamerInterface(QueryType query_type) {
99 query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} { 1407 switch (query_type) {
100 const vk::Device* logical = &cache.GetDevice().GetLogical(); 1408 case QueryType::Payload:
101 cache.GetScheduler().Record([logical, query_ = query](vk::CommandBuffer cmdbuf) { 1409 return &impl->guest_streamer;
102 const bool use_precise = Settings::IsGPULevelHigh(); 1410 case QueryType::ZPassPixelCount64:
103 logical->ResetQueryPool(query_.first, query_.second, 1); 1411 return &impl->sample_streamer;
104 cmdbuf.BeginQuery(query_.first, query_.second, 1412 case QueryType::StreamingByteCount:
105 use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); 1413 return &impl->tfb_streamer;
106 }); 1414 case QueryType::StreamingPrimitivesNeeded:
1415 case QueryType::VtgPrimitivesOut:
1416 case QueryType::StreamingPrimitivesSucceeded:
1417 return &impl->primitives_succeeded_streamer;
1418 case QueryType::StreamingPrimitivesNeededMinusSucceeded:
1419 return &impl->primitives_needed_minus_suceeded_streamer;
1420 default:
1421 return nullptr;
1422 }
107} 1423}
108 1424
109HostCounter::~HostCounter() { 1425void QueryCacheRuntime::Barriers(bool is_prebarrier) {
110 cache.Reserve(type, query); 1426 static constexpr VkMemoryBarrier READ_BARRIER{
1427 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1428 .pNext = nullptr,
1429 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
1430 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
1431 };
1432 static constexpr VkMemoryBarrier WRITE_BARRIER{
1433 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1434 .pNext = nullptr,
1435 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
1436 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
1437 };
1438 if (is_prebarrier) {
1439 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1440 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
1441 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
1442 });
1443 } else {
1444 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1445 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
1446 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
1447 });
1448 }
111} 1449}
112 1450
113void HostCounter::EndQuery() { 1451template <typename SyncValuesType>
114 cache.GetScheduler().Record([query_ = query](vk::CommandBuffer cmdbuf) { 1452void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer) {
115 cmdbuf.EndQuery(query_.first, query_.second); 1453 if (values.size() == 0) {
1454 return;
1455 }
1456 impl->redirect_cache.clear();
1457 impl->little_cache.clear();
1458 size_t total_size = 0;
1459 for (auto& sync_val : values) {
1460 total_size += sync_val.size;
1461 bool found = false;
1462 VAddr base = Common::AlignDown(sync_val.address, Core::Memory::YUZU_PAGESIZE);
1463 VAddr base_end = base + Core::Memory::YUZU_PAGESIZE;
1464 for (size_t i = 0; i < impl->little_cache.size(); i++) {
1465 const auto set_found = [&] {
1466 impl->redirect_cache.push_back(i);
1467 found = true;
1468 };
1469 auto& loc = impl->little_cache[i];
1470 if (base < loc.second && loc.first < base_end) {
1471 set_found();
1472 break;
1473 }
1474 if (loc.first == base_end) {
1475 loc.first = base;
1476 set_found();
1477 break;
1478 }
1479 if (loc.second == base) {
1480 loc.second = base_end;
1481 set_found();
1482 break;
1483 }
1484 }
1485 if (!found) {
1486 impl->redirect_cache.push_back(impl->little_cache.size());
1487 impl->little_cache.emplace_back(base, base_end);
1488 }
1489 }
1490
1491 // Vulkan part.
1492 std::scoped_lock lk(impl->buffer_cache.mutex);
1493 impl->buffer_cache.BufferOperations([&] {
1494 impl->buffers_to_upload_to.clear();
1495 for (auto& pair : impl->little_cache) {
1496 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1497 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1498 const auto [buffer, offset] = impl->buffer_cache.ObtainCPUBuffer(
1499 pair.first, static_cast<u32>(pair.second - pair.first), sync_info, post_op);
1500 impl->buffers_to_upload_to.emplace_back(buffer->Handle(), offset);
1501 }
116 }); 1502 });
117}
118 1503
119u64 HostCounter::BlockingQuery(bool async) const { 1504 VkBuffer src_buffer;
120 if (!async) { 1505 [[maybe_unused]] StagingBufferRef ref;
121 cache.GetScheduler().Wait(tick); 1506 impl->copies_setup.clear();
122 } 1507 impl->copies_setup.resize(impl->little_cache.size());
123 u64 data; 1508 if constexpr (SyncValuesType::GeneratesBaseBuffer) {
124 const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( 1509 ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload);
125 query.first, query.second, 1, sizeof(data), &data, sizeof(data), 1510 size_t current_offset = ref.offset;
126 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); 1511 size_t accumulated_size = 0;
127 1512 for (size_t i = 0; i < values.size(); i++) {
128 switch (query_result) { 1513 size_t which_copy = impl->redirect_cache[i];
129 case VK_SUCCESS: 1514 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
130 return data; 1515 .srcOffset = current_offset + accumulated_size,
131 case VK_ERROR_DEVICE_LOST: 1516 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
132 cache.GetDevice().ReportLoss(); 1517 impl->little_cache[which_copy].first,
133 [[fallthrough]]; 1518 .size = values[i].size,
134 default: 1519 });
135 throw vk::Exception(query_result); 1520 std::memcpy(ref.mapped_span.data() + accumulated_size, &values[i].value,
1521 values[i].size);
1522 accumulated_size += values[i].size;
1523 }
1524 src_buffer = ref.buffer;
1525 } else {
1526 for (size_t i = 0; i < values.size(); i++) {
1527 size_t which_copy = impl->redirect_cache[i];
1528 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
1529 .srcOffset = values[i].offset,
1530 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
1531 impl->little_cache[which_copy].first,
1532 .size = values[i].size,
1533 });
1534 }
1535 src_buffer = base_src_buffer;
136 } 1536 }
1537
1538 impl->scheduler.RequestOutsideRenderPassOperationContext();
1539 impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to),
1540 vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
1541 size_t size = dst_buffers.size();
1542 for (size_t i = 0; i < size; i++) {
1543 cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);
1544 }
1545 });
137} 1546}
138 1547
139} // namespace Vulkan 1548} // namespace Vulkan
1549
1550namespace VideoCommon {
1551
1552template class QueryCacheBase<Vulkan::QueryCacheParams>;
1553
1554} // namespace VideoCommon
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index c1b9552eb..e9a1ea169 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -1,101 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4#pragma once 4#pragma once
5 5
6#include <cstddef>
7#include <memory> 6#include <memory>
8#include <utility>
9#include <vector>
10 7
11#include "common/common_types.h" 8#include "video_core/query_cache/query_cache_base.h"
12#include "video_core/query_cache.h" 9#include "video_core/renderer_vulkan/vk_buffer_cache.h"
13#include "video_core/renderer_vulkan/vk_resource_pool.h"
14#include "video_core/vulkan_common/vulkan_wrapper.h"
15 10
16namespace VideoCore { 11namespace VideoCore {
17class RasterizerInterface; 12class RasterizerInterface;
18} 13}
19 14
15namespace VideoCommon {
16class StreamerInterface;
17}
18
20namespace Vulkan { 19namespace Vulkan {
21 20
22class CachedQuery;
23class Device; 21class Device;
24class HostCounter;
25class QueryCache;
26class Scheduler; 22class Scheduler;
23class StagingBufferPool;
27 24
28using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; 25struct QueryCacheRuntimeImpl;
29 26
30class QueryPool final : public ResourcePool { 27class QueryCacheRuntime {
31public: 28public:
32 explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type); 29 explicit QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
33 ~QueryPool() override; 30 Core::Memory::Memory& cpu_memory_,
31 Vulkan::BufferCache& buffer_cache_, const Device& device_,
32 const MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
33 StagingBufferPool& staging_pool_,
34 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
35 DescriptorPool& descriptor_pool);
36 ~QueryCacheRuntime();
34 37
35 std::pair<VkQueryPool, u32> Commit(); 38 template <typename SyncValuesType>
39 void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
36 40
37 void Reserve(std::pair<VkQueryPool, u32> query); 41 void Barriers(bool is_prebarrier);
38 42
39protected: 43 void EndHostConditionalRendering();
40 void Allocate(std::size_t begin, std::size_t end) override;
41 44
42private: 45 void PauseHostConditionalRendering();
43 static constexpr std::size_t GROW_STEP = 512;
44 46
45 const Device& device; 47 void ResumeHostConditionalRendering();
46 const VideoCore::QueryType type;
47 48
48 std::vector<vk::QueryPool> pools; 49 bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty);
49 std::vector<bool> usage;
50};
51 50
52class QueryCache final 51 bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
53 : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { 52 VideoCommon::LookupData object_2, bool qc_dirty,
54public: 53 bool equal_check);
55 explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
56 Core::Memory::Memory& cpu_memory_, const Device& device_,
57 Scheduler& scheduler_);
58 ~QueryCache();
59
60 std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
61 54
62 void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query); 55 VideoCommon::StreamerInterface* GetStreamerInterface(VideoCommon::QueryType query_type);
63 56
64 const Device& GetDevice() const noexcept { 57 void Bind3DEngine(Tegra::Engines::Maxwell3D* maxwell3d);
65 return device;
66 }
67 58
68 Scheduler& GetScheduler() const noexcept { 59 template <typename Func>
69 return scheduler; 60 void View3DRegs(Func&& func);
70 }
71 61
72private: 62private:
73 const Device& device; 63 void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal);
74 Scheduler& scheduler; 64 void HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal);
75 std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; 65 friend struct QueryCacheRuntimeImpl;
66 std::unique_ptr<QueryCacheRuntimeImpl> impl;
76}; 67};
77 68
78class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { 69struct QueryCacheParams {
79public: 70 using RuntimeType = typename Vulkan::QueryCacheRuntime;
80 explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
81 VideoCore::QueryType type_);
82 ~HostCounter();
83
84 void EndQuery();
85
86private:
87 u64 BlockingQuery(bool async = false) const override;
88
89 QueryCache& cache;
90 const VideoCore::QueryType type;
91 const std::pair<VkQueryPool, u32> query;
92 const u64 tick;
93}; 71};
94 72
95class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> { 73using QueryCache = VideoCommon::QueryCacheBase<QueryCacheParams>;
96public:
97 explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
98 : CachedQueryBase{cpu_addr_, host_ptr_} {}
99};
100 74
101} // namespace Vulkan 75} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 01e76a82c..1628d76d6 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -24,6 +24,7 @@
24#include "video_core/renderer_vulkan/vk_compute_pipeline.h" 24#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
25#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 25#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
26#include "video_core/renderer_vulkan/vk_pipeline_cache.h" 26#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
27#include "video_core/renderer_vulkan/vk_query_cache.h"
27#include "video_core/renderer_vulkan/vk_rasterizer.h" 28#include "video_core/renderer_vulkan/vk_rasterizer.h"
28#include "video_core/renderer_vulkan/vk_scheduler.h" 29#include "video_core/renderer_vulkan/vk_scheduler.h"
29#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" 30#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -170,9 +171,11 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
170 buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool, 171 buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
171 guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool), 172 guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool),
172 buffer_cache(*this, cpu_memory_, buffer_cache_runtime), 173 buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
174 query_cache_runtime(this, cpu_memory_, buffer_cache, device, memory_allocator, scheduler,
175 staging_pool, compute_pass_descriptor_queue, descriptor_pool),
176 query_cache(gpu, *this, cpu_memory_, query_cache_runtime),
173 pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue, 177 pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue,
174 render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), 178 render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
175 query_cache{*this, cpu_memory_, device, scheduler},
176 accelerate_dma(buffer_cache, texture_cache, scheduler), 179 accelerate_dma(buffer_cache, texture_cache, scheduler),
177 fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), 180 fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
178 wfi_event(device.GetLogical().CreateEvent()) { 181 wfi_event(device.GetLogical().CreateEvent()) {
@@ -189,14 +192,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
189 FlushWork(); 192 FlushWork();
190 gpu_memory->FlushCaching(); 193 gpu_memory->FlushCaching();
191 194
192#if ANDROID 195 query_cache.NotifySegment(true);
193 if (Settings::IsGPULevelHigh()) {
194 // This is problematic on Android, disable on GPU Normal.
195 query_cache.UpdateCounters();
196 }
197#else
198 query_cache.UpdateCounters();
199#endif
200 196
201 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()}; 197 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
202 if (!pipeline) { 198 if (!pipeline) {
@@ -207,13 +203,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
207 pipeline->SetEngine(maxwell3d, gpu_memory); 203 pipeline->SetEngine(maxwell3d, gpu_memory);
208 pipeline->Configure(is_indexed); 204 pipeline->Configure(is_indexed);
209 205
210 BeginTransformFeedback();
211
212 UpdateDynamicStates(); 206 UpdateDynamicStates();
213 207
208 HandleTransformFeedback();
209 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
210 maxwell3d->regs.zpass_pixel_count_enable);
214 draw_func(); 211 draw_func();
215
216 EndTransformFeedback();
217} 212}
218 213
219void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { 214void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
@@ -241,6 +236,14 @@ void RasterizerVulkan::DrawIndirect() {
241 const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); 236 const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
242 const auto& buffer = indirect_buffer.first; 237 const auto& buffer = indirect_buffer.first;
243 const auto& offset = indirect_buffer.second; 238 const auto& offset = indirect_buffer.second;
239 if (params.is_byte_count) {
240 scheduler.Record([buffer_obj = buffer->Handle(), offset,
241 stride = params.stride](vk::CommandBuffer cmdbuf) {
242 cmdbuf.DrawIndirectByteCountEXT(1, 0, buffer_obj, offset, 0,
243 static_cast<u32>(stride));
244 });
245 return;
246 }
244 if (params.include_count) { 247 if (params.include_count) {
245 const auto count = buffer_cache.GetDrawIndirectCount(); 248 const auto count = buffer_cache.GetDrawIndirectCount();
246 const auto& draw_buffer = count.first; 249 const auto& draw_buffer = count.first;
@@ -280,20 +283,15 @@ void RasterizerVulkan::DrawTexture() {
280 SCOPE_EXIT({ gpu.TickWork(); }); 283 SCOPE_EXIT({ gpu.TickWork(); });
281 FlushWork(); 284 FlushWork();
282 285
283#if ANDROID 286 query_cache.NotifySegment(true);
284 if (Settings::IsGPULevelHigh()) {
285 // This is problematic on Android, disable on GPU Normal.
286 query_cache.UpdateCounters();
287 }
288#else
289 query_cache.UpdateCounters();
290#endif
291 287
292 texture_cache.SynchronizeGraphicsDescriptors(); 288 texture_cache.SynchronizeGraphicsDescriptors();
293 texture_cache.UpdateRenderTargets(false); 289 texture_cache.UpdateRenderTargets(false);
294 290
295 UpdateDynamicStates(); 291 UpdateDynamicStates();
296 292
293 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
294 maxwell3d->regs.zpass_pixel_count_enable);
297 const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); 295 const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState();
298 const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); 296 const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
299 const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); 297 const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
@@ -316,14 +314,9 @@ void RasterizerVulkan::Clear(u32 layer_count) {
316 FlushWork(); 314 FlushWork();
317 gpu_memory->FlushCaching(); 315 gpu_memory->FlushCaching();
318 316
319#if ANDROID 317 query_cache.NotifySegment(true);
320 if (Settings::IsGPULevelHigh()) { 318 query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
321 // This is problematic on Android, disable on GPU Normal. 319 maxwell3d->regs.zpass_pixel_count_enable);
322 query_cache.UpdateCounters();
323 }
324#else
325 query_cache.UpdateCounters();
326#endif
327 320
328 auto& regs = maxwell3d->regs; 321 auto& regs = maxwell3d->regs;
329 const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B || 322 const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B ||
@@ -482,13 +475,13 @@ void RasterizerVulkan::DispatchCompute() {
482 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); 475 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
483} 476}
484 477
485void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) { 478void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
486 query_cache.ResetCounter(type); 479 query_cache.CounterReset(type);
487} 480}
488 481
489void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 482void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
490 std::optional<u64> timestamp) { 483 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
491 query_cache.Query(gpu_addr, type, timestamp); 484 query_cache.CounterReport(gpu_addr, type, flags, payload, subreport);
492} 485}
493 486
494void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 487void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -669,8 +662,8 @@ void RasterizerVulkan::SignalReference() {
669 fence_manager.SignalReference(); 662 fence_manager.SignalReference();
670} 663}
671 664
672void RasterizerVulkan::ReleaseFences() { 665void RasterizerVulkan::ReleaseFences(bool force) {
673 fence_manager.WaitPendingFences(); 666 fence_manager.WaitPendingFences(force);
674} 667}
675 668
676void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size, 669void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size,
@@ -694,6 +687,8 @@ void RasterizerVulkan::WaitForIdle() {
694 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; 687 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
695 } 688 }
696 689
690 query_cache.NotifyWFI();
691
697 scheduler.RequestOutsideRenderPassOperationContext(); 692 scheduler.RequestOutsideRenderPassOperationContext();
698 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) { 693 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) {
699 cmdbuf.SetEvent(event, flags); 694 cmdbuf.SetEvent(event, flags);
@@ -737,19 +732,7 @@ void RasterizerVulkan::TickFrame() {
737 732
738bool RasterizerVulkan::AccelerateConditionalRendering() { 733bool RasterizerVulkan::AccelerateConditionalRendering() {
739 gpu_memory->FlushCaching(); 734 gpu_memory->FlushCaching();
740 if (Settings::IsGPULevelHigh()) { 735 return query_cache.AccelerateHostConditionalRendering();
741 // TODO(Blinkhawk): Reimplement Host conditional rendering.
742 return false;
743 }
744 // Medium / Low Hack: stub any checks on queries written into the buffer cache.
745 const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()};
746 Maxwell::ReportSemaphore::Compare cmp;
747 if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp),
748 VideoCommon::CacheType::BufferCache |
749 VideoCommon::CacheType::QueryCache)) {
750 return true;
751 }
752 return false;
753} 736}
754 737
755bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, 738bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
@@ -795,6 +778,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
795 if (!image_view) { 778 if (!image_view) {
796 return false; 779 return false;
797 } 780 }
781 query_cache.NotifySegment(false);
798 screen_info.image = image_view->ImageHandle(); 782 screen_info.image = image_view->ImageHandle();
799 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D); 783 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D);
800 screen_info.width = image_view->size.width; 784 screen_info.width = image_view->size.width;
@@ -933,31 +917,18 @@ void RasterizerVulkan::UpdateDynamicStates() {
933 } 917 }
934} 918}
935 919
936void RasterizerVulkan::BeginTransformFeedback() { 920void RasterizerVulkan::HandleTransformFeedback() {
937 const auto& regs = maxwell3d->regs; 921 const auto& regs = maxwell3d->regs;
938 if (regs.transform_feedback_enabled == 0) {
939 return;
940 }
941 if (!device.IsExtTransformFeedbackSupported()) { 922 if (!device.IsExtTransformFeedbackSupported()) {
942 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); 923 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
943 return; 924 return;
944 } 925 }
945 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || 926 query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
946 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); 927 regs.transform_feedback_enabled);
947 scheduler.Record( 928 if (regs.transform_feedback_enabled != 0) {
948 [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); 929 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
949} 930 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
950
951void RasterizerVulkan::EndTransformFeedback() {
952 const auto& regs = maxwell3d->regs;
953 if (regs.transform_feedback_enabled == 0) {
954 return;
955 }
956 if (!device.IsExtTransformFeedbackSupported()) {
957 return;
958 } 931 }
959 scheduler.Record(
960 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
961} 932}
962 933
963void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { 934void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
@@ -1043,15 +1014,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
1043 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || 1014 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM ||
1044 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || 1015 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM ||
1045 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; 1016 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM;
1046 if (is_d24 && !device.SupportsD24DepthBuffer()) { 1017 bool force_unorm = ([&] {
1018 if (!is_d24 || device.SupportsD24DepthBuffer()) {
1019 return false;
1020 }
1021 if (device.IsExtDepthBiasControlSupported()) {
1022 return true;
1023 }
1024 if (!Settings::values.renderer_amdvlk_depth_bias_workaround) {
1025 return false;
1026 }
1047 // the base formulas can be obtained from here: 1027 // the base formulas can be obtained from here:
1048 // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias 1028 // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
1049 const double rescale_factor = 1029 const double rescale_factor =
1050 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127)); 1030 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127));
1051 units = static_cast<float>(static_cast<double>(units) * rescale_factor); 1031 units = static_cast<float>(static_cast<double>(units) * rescale_factor);
1052 } 1032 return false;
1033 })();
1053 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, 1034 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
1054 factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) { 1035 factor = regs.slope_scale_depth_bias, force_unorm,
1036 precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) {
1037 if (force_unorm) {
1038 VkDepthBiasRepresentationInfoEXT info{
1039 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT,
1040 .pNext = nullptr,
1041 .depthBiasRepresentation =
1042 VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT,
1043 .depthBiasExact = precise ? VK_TRUE : VK_FALSE,
1044 };
1045 cmdbuf.SetDepthBias(constant, clamp, factor, &info);
1046 return;
1047 }
1055 cmdbuf.SetDepthBias(constant, clamp, factor); 1048 cmdbuf.SetDepthBias(constant, clamp, factor);
1056 }); 1049 });
1057} 1050}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index b31982485..ad069556c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -84,8 +84,9 @@ public:
84 void DrawTexture() override; 84 void DrawTexture() override;
85 void Clear(u32 layer_count) override; 85 void Clear(u32 layer_count) override;
86 void DispatchCompute() override; 86 void DispatchCompute() override;
87 void ResetCounter(VideoCore::QueryType type) override; 87 void ResetCounter(VideoCommon::QueryType type) override;
88 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; 88 void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
89 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
89 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; 90 void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
90 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; 91 void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
91 void FlushAll() override; 92 void FlushAll() override;
@@ -106,7 +107,7 @@ public:
106 void SyncOperation(std::function<void()>&& func) override; 107 void SyncOperation(std::function<void()>&& func) override;
107 void SignalSyncPoint(u32 value) override; 108 void SignalSyncPoint(u32 value) override;
108 void SignalReference() override; 109 void SignalReference() override;
109 void ReleaseFences() override; 110 void ReleaseFences(bool force = true) override;
110 void FlushAndInvalidateRegion( 111 void FlushAndInvalidateRegion(
111 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 112 VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
112 void WaitForIdle() override; 113 void WaitForIdle() override;
@@ -146,9 +147,7 @@ private:
146 147
147 void UpdateDynamicStates(); 148 void UpdateDynamicStates();
148 149
149 void BeginTransformFeedback(); 150 void HandleTransformFeedback();
150
151 void EndTransformFeedback();
152 151
153 void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs); 152 void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
154 void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs); 153 void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -195,8 +194,9 @@ private:
195 TextureCache texture_cache; 194 TextureCache texture_cache;
196 BufferCacheRuntime buffer_cache_runtime; 195 BufferCacheRuntime buffer_cache_runtime;
197 BufferCache buffer_cache; 196 BufferCache buffer_cache;
198 PipelineCache pipeline_cache; 197 QueryCacheRuntime query_cache_runtime;
199 QueryCache query_cache; 198 QueryCache query_cache;
199 PipelineCache pipeline_cache;
200 AccelerateDMA accelerate_dma; 200 AccelerateDMA accelerate_dma;
201 FenceManager fence_manager; 201 FenceManager fence_manager;
202 202
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 89fd31b4f..3be7837f4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -243,10 +243,10 @@ void Scheduler::AllocateNewContext() {
243#if ANDROID 243#if ANDROID
244 if (Settings::IsGPULevelHigh()) { 244 if (Settings::IsGPULevelHigh()) {
245 // This is problematic on Android, disable on GPU Normal. 245 // This is problematic on Android, disable on GPU Normal.
246 query_cache->UpdateCounters(); 246 query_cache->NotifySegment(true);
247 } 247 }
248#else 248#else
249 query_cache->UpdateCounters(); 249 query_cache->NotifySegment(true);
250#endif 250#endif
251 } 251 }
252} 252}
@@ -261,11 +261,12 @@ void Scheduler::EndPendingOperations() {
261#if ANDROID 261#if ANDROID
262 if (Settings::IsGPULevelHigh()) { 262 if (Settings::IsGPULevelHigh()) {
263 // This is problematic on Android, disable on GPU Normal. 263 // This is problematic on Android, disable on GPU Normal.
264 query_cache->DisableStreams(); 264 // query_cache->DisableStreams();
265 } 265 }
266#else 266#else
267 query_cache->DisableStreams(); 267 // query_cache->DisableStreams();
268#endif 268#endif
269 query_cache->NotifySegment(false);
269 EndRenderPass(); 270 EndRenderPass();
270} 271}
271 272
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 475c682eb..da03803aa 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -17,6 +17,11 @@
17#include "video_core/renderer_vulkan/vk_master_semaphore.h" 17#include "video_core/renderer_vulkan/vk_master_semaphore.h"
18#include "video_core/vulkan_common/vulkan_wrapper.h" 18#include "video_core/vulkan_common/vulkan_wrapper.h"
19 19
20namespace VideoCommon {
21template <typename Trait>
22class QueryCacheBase;
23}
24
20namespace Vulkan { 25namespace Vulkan {
21 26
22class CommandPool; 27class CommandPool;
@@ -24,7 +29,8 @@ class Device;
24class Framebuffer; 29class Framebuffer;
25class GraphicsPipeline; 30class GraphicsPipeline;
26class StateTracker; 31class StateTracker;
27class QueryCache; 32
33struct QueryCacheParams;
28 34
29/// The scheduler abstracts command buffer and fence management with an interface that's able to do 35/// The scheduler abstracts command buffer and fence management with an interface that's able to do
30/// OpenGL-like operations on Vulkan command buffers. 36/// OpenGL-like operations on Vulkan command buffers.
@@ -63,7 +69,7 @@ public:
63 void InvalidateState(); 69 void InvalidateState();
64 70
65 /// Assigns the query cache. 71 /// Assigns the query cache.
66 void SetQueryCache(QueryCache& query_cache_) { 72 void SetQueryCache(VideoCommon::QueryCacheBase<QueryCacheParams>& query_cache_) {
67 query_cache = &query_cache_; 73 query_cache = &query_cache_;
68 } 74 }
69 75
@@ -219,7 +225,7 @@ private:
219 std::unique_ptr<MasterSemaphore> master_semaphore; 225 std::unique_ptr<MasterSemaphore> master_semaphore;
220 std::unique_ptr<CommandPool> command_pool; 226 std::unique_ptr<CommandPool> command_pool;
221 227
222 QueryCache* query_cache = nullptr; 228 VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
223 229
224 vk::CommandBuffer current_cmdbuf; 230 vk::CommandBuffer current_cmdbuf;
225 231
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index b3e17c332..71fdec809 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -120,19 +120,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
120 return usage; 120 return usage;
121} 121}
122 122
123/// Returns the preferred format for a VkImage
124[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) {
125 switch (format) {
126 case PixelFormat::A8B8G8R8_SRGB:
127 return PixelFormat::A8B8G8R8_UNORM;
128 default:
129 return format;
130 }
131}
132
133[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { 123[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
134 const PixelFormat format = StorageFormat(info.format); 124 const auto format_info =
135 const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); 125 MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format);
136 VkImageCreateFlags flags{}; 126 VkImageCreateFlags flags{};
137 if (info.type == ImageType::e2D && info.resources.layers >= 6 && 127 if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
138 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { 128 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) {
@@ -157,7 +147,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
157 .arrayLayers = static_cast<u32>(info.resources.layers), 147 .arrayLayers = static_cast<u32>(info.resources.layers),
158 .samples = ConvertSampleCount(info.num_samples), 148 .samples = ConvertSampleCount(info.num_samples),
159 .tiling = VK_IMAGE_TILING_OPTIMAL, 149 .tiling = VK_IMAGE_TILING_OPTIMAL,
160 .usage = ImageUsageFlags(format_info, format), 150 .usage = ImageUsageFlags(format_info, info.format),
161 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 151 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
162 .queueFamilyIndexCount = 0, 152 .queueFamilyIndexCount = 0,
163 .pQueueFamilyIndices = nullptr, 153 .pQueueFamilyIndices = nullptr,
@@ -186,6 +176,36 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
186 return allocator.CreateImage(image_ci); 176 return allocator.CreateImage(image_ci);
187} 177}
188 178
179[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
180 VkFormat format) {
181 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
182 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
183 .pNext = nullptr,
184 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
185 };
186 return device.CreateImageView(VkImageViewCreateInfo{
187 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
188 .pNext = &storage_image_view_usage_create_info,
189 .flags = 0,
190 .image = image,
191 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
192 .format = format,
193 .components{
194 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
195 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
196 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
197 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
198 },
199 .subresourceRange{
200 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
201 .baseMipLevel = level,
202 .levelCount = 1,
203 .baseArrayLayer = 0,
204 .layerCount = VK_REMAINING_ARRAY_LAYERS,
205 },
206 });
207}
208
189[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { 209[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
190 switch (VideoCore::Surface::GetFormatType(format)) { 210 switch (VideoCore::Surface::GetFormatType(format)) {
191 case VideoCore::Surface::SurfaceType::ColorTexture: 211 case VideoCore::Surface::SurfaceType::ColorTexture:
@@ -600,7 +620,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
600} 620}
601 621
602void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, 622void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
603 bool emulate_bgr565) { 623 bool emulate_bgr565, bool emulate_a4b4g4r4) {
604 switch (format) { 624 switch (format) {
605 case PixelFormat::A1B5G5R5_UNORM: 625 case PixelFormat::A1B5G5R5_UNORM:
606 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); 626 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@@ -616,6 +636,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
616 case PixelFormat::G4R4_UNORM: 636 case PixelFormat::G4R4_UNORM:
617 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); 637 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
618 break; 638 break;
639 case PixelFormat::A4B4G4R4_UNORM:
640 if (emulate_a4b4g4r4) {
641 std::ranges::reverse(swizzle);
642 }
643 break;
619 default: 644 default:
620 break; 645 break;
621 } 646 }
@@ -822,6 +847,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
822 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, 847 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
823 compute_pass_descriptor_queue, memory_allocator); 848 compute_pass_descriptor_queue, memory_allocator);
824 } 849 }
850 if (device.IsStorageImageMultisampleSupported()) {
851 msaa_copy_pass = std::make_unique<MSAACopyPass>(
852 device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
853 }
825 if (!device.IsKhrImageFormatListSupported()) { 854 if (!device.IsKhrImageFormatListSupported()) {
826 return; 855 return;
827 } 856 }
@@ -1044,15 +1073,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
1044 dst_region, src_region, filter, operation); 1073 dst_region, src_region, filter, operation);
1045 return; 1074 return;
1046 } 1075 }
1076 ASSERT(src.format == dst.format);
1047 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { 1077 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1048 if (!device.IsBlitDepthStencilSupported()) { 1078 const auto format = src.format;
1079 const auto can_blit_depth_stencil = [this, format] {
1080 switch (format) {
1081 case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
1082 case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
1083 return device.IsBlitDepth24Stencil8Supported();
1084 case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
1085 return device.IsBlitDepth32Stencil8Supported();
1086 default:
1087 UNREACHABLE();
1088 }
1089 }();
1090 if (!can_blit_depth_stencil) {
1049 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); 1091 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
1050 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), 1092 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
1051 dst_region, src_region, filter, operation); 1093 dst_region, src_region, filter, operation);
1052 return; 1094 return;
1053 } 1095 }
1054 } 1096 }
1055 ASSERT(src.format == dst.format);
1056 ASSERT(!(is_dst_msaa && !is_src_msaa)); 1097 ASSERT(!(is_dst_msaa && !is_src_msaa));
1057 ASSERT(operation == Fermi2D::Operation::SrcCopy); 1098 ASSERT(operation == Fermi2D::Operation::SrcCopy);
1058 1099
@@ -1278,7 +1319,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
1278 1319
1279void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, 1320void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
1280 std::span<const VideoCommon::ImageCopy> copies) { 1321 std::span<const VideoCommon::ImageCopy> copies) {
1281 UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan."); 1322 const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
1323 if (msaa_copy_pass) {
1324 return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa);
1325 }
1326 UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
1282} 1327}
1283 1328
1284u64 TextureCacheRuntime::GetDeviceLocalMemory() const { 1329u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
@@ -1326,39 +1371,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
1326 if (runtime->device.HasDebuggingToolAttached()) { 1371 if (runtime->device.HasDebuggingToolAttached()) {
1327 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); 1372 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
1328 } 1373 }
1329 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
1330 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
1331 .pNext = nullptr,
1332 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
1333 };
1334 current_image = *original_image; 1374 current_image = *original_image;
1375 storage_image_views.resize(info.resources.levels);
1335 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && 1376 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
1336 Settings::values.astc_recompression.GetValue() == 1377 Settings::values.astc_recompression.GetValue() ==
1337 Settings::AstcRecompression::Uncompressed) { 1378 Settings::AstcRecompression::Uncompressed) {
1338 const auto& device = runtime->device.GetLogical(); 1379 const auto& device = runtime->device.GetLogical();
1339 storage_image_views.reserve(info.resources.levels);
1340 for (s32 level = 0; level < info.resources.levels; ++level) { 1380 for (s32 level = 0; level < info.resources.levels; ++level) {
1341 storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{ 1381 storage_image_views[level] =
1342 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1382 MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
1343 .pNext = &storage_image_view_usage_create_info,
1344 .flags = 0,
1345 .image = *original_image,
1346 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
1347 .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
1348 .components{
1349 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
1350 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
1351 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
1352 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
1353 },
1354 .subresourceRange{
1355 .aspectMask = aspect_mask,
1356 .baseMipLevel = static_cast<u32>(level),
1357 .levelCount = 1,
1358 .baseArrayLayer = 0,
1359 .layerCount = VK_REMAINING_ARRAY_LAYERS,
1360 },
1361 }));
1362 } 1383 }
1363 } 1384 }
1364} 1385}
@@ -1489,6 +1510,17 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
1489 DownloadMemory(buffers, offsets, copies); 1510 DownloadMemory(buffers, offsets, copies);
1490} 1511}
1491 1512
1513VkImageView Image::StorageImageView(s32 level) noexcept {
1514 auto& view = storage_image_views[level];
1515 if (!view) {
1516 const auto format_info =
1517 MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
1518 view =
1519 MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format);
1520 }
1521 return *view;
1522}
1523
1492bool Image::IsRescaled() const noexcept { 1524bool Image::IsRescaled() const noexcept {
1493 return True(flags & ImageFlagBits::Rescaled); 1525 return True(flags & ImageFlagBits::Rescaled);
1494} 1526}
@@ -1626,8 +1658,8 @@ bool Image::NeedsScaleHelper() const {
1626 return true; 1658 return true;
1627 } 1659 }
1628 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; 1660 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal;
1629 const PixelFormat format = StorageFormat(info.format); 1661 const auto vk_format =
1630 const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format; 1662 MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, info.format).format;
1631 const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; 1663 const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
1632 const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT); 1664 const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT);
1633 return needs_blit_helper; 1665 return needs_blit_helper;
@@ -1649,7 +1681,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1649 }; 1681 };
1650 if (!info.IsRenderTarget()) { 1682 if (!info.IsRenderTarget()) {
1651 swizzle = info.Swizzle(); 1683 swizzle = info.Swizzle();
1652 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); 1684 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
1685 !device->IsExt4444FormatsSupported());
1653 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { 1686 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
1654 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); 1687 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
1655 } 1688 }
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 565ce19a9..d6c5a15cc 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -117,6 +117,7 @@ public:
117 BlitImageHelper& blit_image_helper; 117 BlitImageHelper& blit_image_helper;
118 RenderPassCache& render_pass_cache; 118 RenderPassCache& render_pass_cache;
119 std::optional<ASTCDecoderPass> astc_decoder_pass; 119 std::optional<ASTCDecoderPass> astc_decoder_pass;
120 std::unique_ptr<MSAACopyPass> msaa_copy_pass;
120 const Settings::ResolutionScalingInfo& resolution; 121 const Settings::ResolutionScalingInfo& resolution;
121 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; 122 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
122 123
@@ -161,15 +162,13 @@ public:
161 return aspect_mask; 162 return aspect_mask;
162 } 163 }
163 164
164 [[nodiscard]] VkImageView StorageImageView(s32 level) const noexcept {
165 return *storage_image_views[level];
166 }
167
168 /// Returns true when the image is already initialized and mark it as initialized 165 /// Returns true when the image is already initialized and mark it as initialized
169 [[nodiscard]] bool ExchangeInitialization() noexcept { 166 [[nodiscard]] bool ExchangeInitialization() noexcept {
170 return std::exchange(initialized, true); 167 return std::exchange(initialized, true);
171 } 168 }
172 169
170 VkImageView StorageImageView(s32 level) noexcept;
171
173 bool IsRescaled() const noexcept; 172 bool IsRescaled() const noexcept;
174 173
175 bool ScaleUp(bool ignore = false); 174 bool ScaleUp(bool ignore = false);
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 11ced6c38..56307d030 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -140,6 +140,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
140 return PixelFormat::D32_FLOAT; 140 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z16, UNORM): 141 case Hash(TextureFormat::Z16, UNORM):
142 return PixelFormat::D16_UNORM; 142 return PixelFormat::D16_UNORM;
143 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
144 return PixelFormat::D16_UNORM;
143 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): 145 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
144 return PixelFormat::S8_UINT_D24_UNORM; 146 return PixelFormat::S8_UINT_D24_UNORM;
145 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): 147 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 617417040..3960b135a 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -76,6 +76,11 @@ constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{
76 VK_FORMAT_UNDEFINED, 76 VK_FORMAT_UNDEFINED,
77}; 77};
78 78
79constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
80 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
81 VK_FORMAT_UNDEFINED,
82};
83
79} // namespace Alternatives 84} // namespace Alternatives
80 85
81enum class NvidiaArchitecture { 86enum class NvidiaArchitecture {
@@ -110,6 +115,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
110 return Alternatives::R8G8B8_SSCALED.data(); 115 return Alternatives::R8G8B8_SSCALED.data();
111 case VK_FORMAT_R32G32B32_SFLOAT: 116 case VK_FORMAT_R32G32B32_SFLOAT:
112 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); 117 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
118 case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
119 return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
113 default: 120 default:
114 return nullptr; 121 return nullptr;
115 } 122 }
@@ -238,6 +245,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
238 VK_FORMAT_R32_SINT, 245 VK_FORMAT_R32_SINT,
239 VK_FORMAT_R32_UINT, 246 VK_FORMAT_R32_UINT,
240 VK_FORMAT_R4G4B4A4_UNORM_PACK16, 247 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
248 VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
241 VK_FORMAT_R4G4_UNORM_PACK8, 249 VK_FORMAT_R4G4_UNORM_PACK8,
242 VK_FORMAT_R5G5B5A1_UNORM_PACK16, 250 VK_FORMAT_R5G5B5A1_UNORM_PACK16,
243 VK_FORMAT_R5G6B5_UNORM_PACK16, 251 VK_FORMAT_R5G6B5_UNORM_PACK16,
@@ -420,7 +428,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
420 first_next = &diagnostics_nv; 428 first_next = &diagnostics_nv;
421 } 429 }
422 430
423 is_blit_depth_stencil_supported = TestDepthStencilBlits(); 431 is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT);
432 is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT);
424 is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); 433 is_optimal_astc_supported = ComputeIsOptimalAstcSupported();
425 is_warp_potentially_bigger = !extensions.subgroup_size_control || 434 is_warp_potentially_bigger = !extensions.subgroup_size_control ||
426 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; 435 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
@@ -774,14 +783,13 @@ bool Device::ComputeIsOptimalAstcSupported() const {
774 return true; 783 return true;
775} 784}
776 785
777bool Device::TestDepthStencilBlits() const { 786bool Device::TestDepthStencilBlits(VkFormat format) const {
778 static constexpr VkFormatFeatureFlags required_features = 787 static constexpr VkFormatFeatureFlags required_features =
779 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; 788 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
780 const auto test_features = [](VkFormatProperties props) { 789 const auto test_features = [](VkFormatProperties props) {
781 return (props.optimalTilingFeatures & required_features) == required_features; 790 return (props.optimalTilingFeatures & required_features) == required_features;
782 }; 791 };
783 return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) && 792 return test_features(format_properties.at(format));
784 test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT));
785} 793}
786 794
787bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, 795bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
@@ -1051,6 +1059,13 @@ void Device::RemoveUnsuitableExtensions() {
1051 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, 1059 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
1052 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 1060 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
1053 1061
1062 // VK_EXT_depth_bias_control
1063 extensions.depth_bias_control =
1064 features.depth_bias_control.depthBiasControl &&
1065 features.depth_bias_control.leastRepresentableValueForceUnormRepresentation;
1066 RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control,
1067 VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME);
1068
1054 // VK_EXT_depth_clip_control 1069 // VK_EXT_depth_clip_control
1055 extensions.depth_clip_control = features.depth_clip_control.depthClipControl; 1070 extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
1056 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, 1071 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 488fdd313..9be612392 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -41,10 +41,12 @@ VK_DEFINE_HANDLE(VmaAllocator)
41// Define all features which may be used by the implementation and require an extension here. 41// Define all features which may be used by the implementation and require an extension here.
42#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ 42#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
43 FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ 43 FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
44 FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
44 FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ 45 FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
45 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ 46 FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
46 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ 47 FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
47 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ 48 FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
49 FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
48 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ 50 FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
49 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ 51 FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
50 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ 52 FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
@@ -60,6 +62,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
60 62
61// Define miscellaneous extensions which may be used by the implementation here. 63// Define miscellaneous extensions which may be used by the implementation here.
62#define FOR_EACH_VK_EXTENSION(EXTENSION) \ 64#define FOR_EACH_VK_EXTENSION(EXTENSION) \
65 EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
63 EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ 66 EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
64 EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ 67 EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
65 EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \ 68 EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
@@ -92,11 +95,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
92 95
93// Define extensions where the absence of the extension may result in a degraded experience. 96// Define extensions where the absence of the extension may result in a degraded experience.
94#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ 97#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \
98 EXTENSION_NAME(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME) \
95 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ 99 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \
100 EXTENSION_NAME(VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME) \
96 EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \ 101 EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \
97 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ 102 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
98 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ 103 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
99 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ 104 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
105 EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
100 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ 106 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
101 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \ 107 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
102 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ 108 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
@@ -143,7 +149,11 @@ VK_DEFINE_HANDLE(VmaAllocator)
143// Define features where the absence of the feature may result in a degraded experience. 149// Define features where the absence of the feature may result in a degraded experience.
144#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ 150#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
145 FEATURE_NAME(custom_border_color, customBorderColors) \ 151 FEATURE_NAME(custom_border_color, customBorderColors) \
152 FEATURE_NAME(depth_bias_control, depthBiasControl) \
153 FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \
154 FEATURE_NAME(depth_bias_control, depthBiasExact) \
146 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ 155 FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
156 FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
147 FEATURE_NAME(index_type_uint8, indexTypeUint8) \ 157 FEATURE_NAME(index_type_uint8, indexTypeUint8) \
148 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ 158 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
149 FEATURE_NAME(provoking_vertex, provokingVertexLast) \ 159 FEATURE_NAME(provoking_vertex, provokingVertexLast) \
@@ -319,6 +329,11 @@ public:
319 return features.shader_float16_int8.shaderInt8; 329 return features.shader_float16_int8.shaderInt8;
320 } 330 }
321 331
332 /// Returns true if the device supports binding multisample images as storage images.
333 bool IsStorageImageMultisampleSupported() const {
334 return features.features.shaderStorageImageMultisample;
335 }
336
322 /// Returns true if the device warp size can potentially be bigger than guest's warp size. 337 /// Returns true if the device warp size can potentially be bigger than guest's warp size.
323 bool IsWarpSizePotentiallyBiggerThanGuest() const { 338 bool IsWarpSizePotentiallyBiggerThanGuest() const {
324 return is_warp_potentially_bigger; 339 return is_warp_potentially_bigger;
@@ -359,9 +374,14 @@ public:
359 return features.features.depthBounds; 374 return features.features.depthBounds;
360 } 375 }
361 376
362 /// Returns true when blitting from and to depth stencil images is supported. 377 /// Returns true when blitting from and to D24S8 images is supported.
363 bool IsBlitDepthStencilSupported() const { 378 bool IsBlitDepth24Stencil8Supported() const {
364 return is_blit_depth_stencil_supported; 379 return is_blit_depth24_stencil8_supported;
380 }
381
382 /// Returns true when blitting from and to D32S8 images is supported.
383 bool IsBlitDepth32Stencil8Supported() const {
384 return is_blit_depth32_stencil8_supported;
365 } 385 }
366 386
367 /// Returns true if the device supports VK_NV_viewport_swizzle. 387 /// Returns true if the device supports VK_NV_viewport_swizzle.
@@ -449,6 +469,11 @@ public:
449 return extensions.depth_clip_control; 469 return extensions.depth_clip_control;
450 } 470 }
451 471
472 /// Returns true if the device supports VK_EXT_depth_bias_control.
473 bool IsExtDepthBiasControlSupported() const {
474 return extensions.depth_bias_control;
475 }
476
452 /// Returns true if the device supports VK_EXT_shader_viewport_index_layer. 477 /// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
453 bool IsExtShaderViewportIndexLayerSupported() const { 478 bool IsExtShaderViewportIndexLayerSupported() const {
454 return extensions.shader_viewport_index_layer; 479 return extensions.shader_viewport_index_layer;
@@ -488,6 +513,11 @@ public:
488 return extensions.extended_dynamic_state3; 513 return extensions.extended_dynamic_state3;
489 } 514 }
490 515
516 /// Returns true if the device supports VK_EXT_4444_formats.
517 bool IsExt4444FormatsSupported() const {
518 return features.format_a4b4g4r4.formatA4B4G4R4;
519 }
520
491 /// Returns true if the device supports VK_EXT_extended_dynamic_state3. 521 /// Returns true if the device supports VK_EXT_extended_dynamic_state3.
492 bool IsExtExtendedDynamicState3BlendingSupported() const { 522 bool IsExtExtendedDynamicState3BlendingSupported() const {
493 return dynamic_state3_blending; 523 return dynamic_state3_blending;
@@ -528,6 +558,10 @@ public:
528 return extensions.shader_atomic_int64; 558 return extensions.shader_atomic_int64;
529 } 559 }
530 560
561 bool IsExtConditionalRendering() const {
562 return extensions.conditional_rendering;
563 }
564
531 bool HasTimelineSemaphore() const; 565 bool HasTimelineSemaphore() const;
532 566
533 /// Returns the minimum supported version of SPIR-V. 567 /// Returns the minimum supported version of SPIR-V.
@@ -600,6 +634,10 @@ public:
600 return features.robustness2.nullDescriptor; 634 return features.robustness2.nullDescriptor;
601 } 635 }
602 636
637 bool HasExactDepthBiasControl() const {
638 return features.depth_bias_control.depthBiasExact;
639 }
640
603 u32 GetMaxVertexInputAttributes() const { 641 u32 GetMaxVertexInputAttributes() const {
604 return properties.properties.limits.maxVertexInputAttributes; 642 return properties.properties.limits.maxVertexInputAttributes;
605 } 643 }
@@ -666,7 +704,7 @@ private:
666 bool ComputeIsOptimalAstcSupported() const; 704 bool ComputeIsOptimalAstcSupported() const;
667 705
668 /// Returns true if the device natively supports blitting depth stencil images. 706 /// Returns true if the device natively supports blitting depth stencil images.
669 bool TestDepthStencilBlits() const; 707 bool TestDepthStencilBlits(VkFormat format) const;
670 708
671private: 709private:
672 VkInstance instance; ///< Vulkan instance. 710 VkInstance instance; ///< Vulkan instance.
@@ -730,25 +768,26 @@ private:
730 VkPhysicalDeviceProperties2 properties2{}; 768 VkPhysicalDeviceProperties2 properties2{};
731 769
732 // Misc features 770 // Misc features
733 bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats. 771 bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats.
734 bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil. 772 bool is_blit_depth24_stencil8_supported{}; ///< Support for blitting from and to D24S8.
735 bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest. 773 bool is_blit_depth32_stencil8_supported{}; ///< Support for blitting from and to D32S8.
736 bool is_integrated{}; ///< Is GPU an iGPU. 774 bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
737 bool is_virtual{}; ///< Is GPU a virtual GPU. 775 bool is_integrated{}; ///< Is GPU an iGPU.
738 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. 776 bool is_virtual{}; ///< Is GPU a virtual GPU.
739 bool has_broken_compute{}; ///< Compute shaders can cause crashes 777 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
740 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit 778 bool has_broken_compute{}; ///< Compute shaders can cause crashes
741 bool has_renderdoc{}; ///< Has RenderDoc attached 779 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
742 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached 780 bool has_renderdoc{}; ///< Has RenderDoc attached
743 bool supports_d24_depth{}; ///< Supports D24 depth buffers. 781 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
744 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. 782 bool supports_d24_depth{}; ///< Supports D24 depth buffers.
745 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation 783 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
746 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. 784 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
747 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. 785 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
748 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. 786 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
749 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. 787 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
750 u64 device_access_memory{}; ///< Total size of device local memory in bytes. 788 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
751 u32 sets_per_pool{}; ///< Sets per Description Pool 789 u64 device_access_memory{}; ///< Total size of device local memory in bytes.
790 u32 sets_per_pool{}; ///< Sets per Description Pool
752 791
753 // Telemetry parameters 792 // Telemetry parameters
754 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. 793 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index c3f388d89..2f3254a97 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -75,6 +75,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
75 X(vkBeginCommandBuffer); 75 X(vkBeginCommandBuffer);
76 X(vkBindBufferMemory); 76 X(vkBindBufferMemory);
77 X(vkBindImageMemory); 77 X(vkBindImageMemory);
78 X(vkCmdBeginConditionalRenderingEXT);
78 X(vkCmdBeginQuery); 79 X(vkCmdBeginQuery);
79 X(vkCmdBeginRenderPass); 80 X(vkCmdBeginRenderPass);
80 X(vkCmdBeginTransformFeedbackEXT); 81 X(vkCmdBeginTransformFeedbackEXT);
@@ -91,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
91 X(vkCmdCopyBufferToImage); 92 X(vkCmdCopyBufferToImage);
92 X(vkCmdCopyImage); 93 X(vkCmdCopyImage);
93 X(vkCmdCopyImageToBuffer); 94 X(vkCmdCopyImageToBuffer);
95 X(vkCmdCopyQueryPoolResults);
94 X(vkCmdDispatch); 96 X(vkCmdDispatch);
95 X(vkCmdDispatchIndirect); 97 X(vkCmdDispatchIndirect);
96 X(vkCmdDraw); 98 X(vkCmdDraw);
@@ -99,6 +101,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
99 X(vkCmdDrawIndexedIndirect); 101 X(vkCmdDrawIndexedIndirect);
100 X(vkCmdDrawIndirectCount); 102 X(vkCmdDrawIndirectCount);
101 X(vkCmdDrawIndexedIndirectCount); 103 X(vkCmdDrawIndexedIndirectCount);
104 X(vkCmdDrawIndirectByteCountEXT);
105 X(vkCmdEndConditionalRenderingEXT);
102 X(vkCmdEndQuery); 106 X(vkCmdEndQuery);
103 X(vkCmdEndRenderPass); 107 X(vkCmdEndRenderPass);
104 X(vkCmdEndTransformFeedbackEXT); 108 X(vkCmdEndTransformFeedbackEXT);
@@ -109,6 +113,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
109 X(vkCmdPushDescriptorSetWithTemplateKHR); 113 X(vkCmdPushDescriptorSetWithTemplateKHR);
110 X(vkCmdSetBlendConstants); 114 X(vkCmdSetBlendConstants);
111 X(vkCmdSetDepthBias); 115 X(vkCmdSetDepthBias);
116 X(vkCmdSetDepthBias2EXT);
112 X(vkCmdSetDepthBounds); 117 X(vkCmdSetDepthBounds);
113 X(vkCmdSetEvent); 118 X(vkCmdSetEvent);
114 X(vkCmdSetScissor); 119 X(vkCmdSetScissor);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 049fa8038..1e3c0fa64 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -185,6 +185,7 @@ struct DeviceDispatch : InstanceDispatch {
185 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; 185 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{};
186 PFN_vkBindBufferMemory vkBindBufferMemory{}; 186 PFN_vkBindBufferMemory vkBindBufferMemory{};
187 PFN_vkBindImageMemory vkBindImageMemory{}; 187 PFN_vkBindImageMemory vkBindImageMemory{};
188 PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{};
188 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; 189 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{};
189 PFN_vkCmdBeginQuery vkCmdBeginQuery{}; 190 PFN_vkCmdBeginQuery vkCmdBeginQuery{};
190 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; 191 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{};
@@ -202,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch {
202 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; 203 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{};
203 PFN_vkCmdCopyImage vkCmdCopyImage{}; 204 PFN_vkCmdCopyImage vkCmdCopyImage{};
204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; 205 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
206 PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults{};
205 PFN_vkCmdDispatch vkCmdDispatch{}; 207 PFN_vkCmdDispatch vkCmdDispatch{};
206 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; 208 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
207 PFN_vkCmdDraw vkCmdDraw{}; 209 PFN_vkCmdDraw vkCmdDraw{};
@@ -210,6 +212,8 @@ struct DeviceDispatch : InstanceDispatch {
210 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{}; 212 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{};
211 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; 213 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
212 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; 214 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
215 PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
216 PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
213 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; 217 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
214 PFN_vkCmdEndQuery vkCmdEndQuery{}; 218 PFN_vkCmdEndQuery vkCmdEndQuery{};
215 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; 219 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{};
@@ -222,6 +226,7 @@ struct DeviceDispatch : InstanceDispatch {
222 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; 226 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
223 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; 227 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
224 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; 228 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
229 PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{};
225 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; 230 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{};
226 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; 231 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{};
227 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; 232 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{};
@@ -1182,6 +1187,13 @@ public:
1182 count_offset, draw_count, stride); 1187 count_offset, draw_count, stride);
1183 } 1188 }
1184 1189
1190 void DrawIndirectByteCountEXT(u32 instance_count, u32 first_instance, VkBuffer counter_buffer,
1191 VkDeviceSize counter_buffer_offset, u32 counter_offset,
1192 u32 stride) {
1193 dld->vkCmdDrawIndirectByteCountEXT(handle, instance_count, first_instance, counter_buffer,
1194 counter_buffer_offset, counter_offset, stride);
1195 }
1196
1185 void ClearAttachments(Span<VkClearAttachment> attachments, 1197 void ClearAttachments(Span<VkClearAttachment> attachments,
1186 Span<VkClearRect> rects) const noexcept { 1198 Span<VkClearRect> rects) const noexcept {
1187 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), 1199 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
@@ -1270,6 +1282,13 @@ public:
1270 regions.data()); 1282 regions.data());
1271 } 1283 }
1272 1284
1285 void CopyQueryPoolResults(VkQueryPool query_pool, u32 first_query, u32 query_count,
1286 VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize stride,
1287 VkQueryResultFlags flags) const noexcept {
1288 dld->vkCmdCopyQueryPoolResults(handle, query_pool, first_query, query_count, dst_buffer,
1289 dst_offset, stride, flags);
1290 }
1291
1273 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, 1292 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
1274 u32 data) const noexcept { 1293 u32 data) const noexcept {
1275 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); 1294 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
@@ -1315,6 +1334,18 @@ public:
1315 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); 1334 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
1316 } 1335 }
1317 1336
1337 void SetDepthBias(float constant_factor, float clamp, float slope_factor,
1338 VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
1339 VkDepthBiasInfoEXT info{
1340 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT,
1341 .pNext = extra,
1342 .depthBiasConstantFactor = constant_factor,
1343 .depthBiasClamp = clamp,
1344 .depthBiasSlopeFactor = slope_factor,
1345 };
1346 dld->vkCmdSetDepthBias2EXT(handle, &info);
1347 }
1348
1318 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { 1349 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
1319 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); 1350 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
1320 } 1351 }
@@ -1448,6 +1479,15 @@ public:
1448 counter_buffers, counter_buffer_offsets); 1479 counter_buffers, counter_buffer_offsets);
1449 } 1480 }
1450 1481
1482 void BeginConditionalRenderingEXT(
1483 const VkConditionalRenderingBeginInfoEXT& info) const noexcept {
1484 dld->vkCmdBeginConditionalRenderingEXT(handle, &info);
1485 }
1486
1487 void EndConditionalRenderingEXT() const noexcept {
1488 dld->vkCmdEndConditionalRenderingEXT(handle);
1489 }
1490
1451 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { 1491 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept {
1452 const VkDebugUtilsLabelEXT label_info{ 1492 const VkDebugUtilsLabelEXT label_info{
1453 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, 1493 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
62 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
63 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
62 ui->enable_graphics_debugging->setEnabled(runtime_lock); 64 ui->enable_graphics_debugging->setEnabled(runtime_lock);
63 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 65 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
64 ui->enable_shader_feedback->setEnabled(runtime_lock); 66 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
111 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
112 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 114 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
113 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 115 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
116 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
114 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 117 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
115 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 118 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
116 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 119 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
18 <rect> 18 <rect>
19 <x>0</x> 19 <x>0</x>
20 <y>0</y> 20 <y>0</y>
21 <width>829</width> 21 <width>842</width>
22 <height>758</height> 22 <height>741</height>
23 </rect> 23 </rect>
24 </property> 24 </property>
25 <layout class="QVBoxLayout" name="verticalLayout_1"> 25 <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
260 <string>Graphics</string> 260 <string>Graphics</string>
261 </property> 261 </property>
262 <layout class="QGridLayout" name="gridLayout_2"> 262 <layout class="QGridLayout" name="gridLayout_2">
263 <item row="3" column="0"> 263 <item row="4" column="0">
264 <widget class="QCheckBox" name="disable_loop_safety_checks"> 264 <widget class="QCheckBox" name="disable_loop_safety_checks">
265 <property name="toolTip"> 265 <property name="toolTip">
266 <string>When checked, it executes shaders without loop logic changes</string> 266 <string>When checked, it executes shaders without loop logic changes</string>
@@ -270,33 +270,53 @@
270 </property> 270 </property>
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="4" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="dump_shaders"> 274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled"> 275 <property name="enabled">
276 <bool>true</bool> 276 <bool>true</bool>
277 </property> 277 </property>
278 <property name="toolTip"> 278 <property name="toolTip">
279 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property> 280 </property>
281 <property name="text"> 281 <property name="text">
282 <string>Dump Game Shaders</string> 282 <string>Disable Macro HLE</string>
283 </property> 283 </property>
284 </widget> 284 </widget>
285 </item> 285 </item>
286 <item row="7" column="0"> 286 <item row="7" column="0">
287 <widget class="QCheckBox" name="disable_macro_hle"> 287 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 288 <property name="enabled">
289 <bool>true</bool> 289 <bool>true</bool>
290 </property> 290 </property>
291 <property name="toolTip"> 291 <property name="toolTip">
292 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> 292 <string>When checked, it will dump all the macro programs of the GPU</string>
293 </property> 293 </property>
294 <property name="text"> 294 <property name="text">
295 <string>Disable Macro HLE</string> 295 <string>Dump Maxwell Macros</string>
296 </property> 296 </property>
297 </widget> 297 </widget>
298 </item> 298 </item>
299 <item row="5" column="0"> 299 <item row="3" column="0">
300 <widget class="QCheckBox" name="enable_nsight_aftermath">
301 <property name="toolTip">
302 <string>When checked, it enables Nsight Aftermath crash dumps</string>
303 </property>
304 <property name="text">
305 <string>Enable Nsight Aftermath</string>
306 </property>
307 </widget>
308 </item>
309 <item row="2" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback">
311 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
313 </property>
314 <property name="text">
315 <string>Enable Shader Feedback</string>
316 </property>
317 </widget>
318 </item>
319 <item row="6" column="0">
300 <widget class="QCheckBox" name="disable_macro_jit"> 320 <widget class="QCheckBox" name="disable_macro_jit">
301 <property name="enabled"> 321 <property name="enabled">
302 <bool>true</bool> 322 <bool>true</bool>
@@ -309,6 +329,22 @@
309 </property> 329 </property>
310 </widget> 330 </widget>
311 </item> 331 </item>
332 <item row="9" column="0">
333 <spacer name="verticalSpacer_5">
334 <property name="orientation">
335 <enum>Qt::Vertical</enum>
336 </property>
337 <property name="sizeType">
338 <enum>QSizePolicy::Preferred</enum>
339 </property>
340 <property name="sizeHint" stdset="0">
341 <size>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property>
346 </spacer>
347 </item>
312 <item row="0" column="0"> 348 <item row="0" column="0">
313 <widget class="QCheckBox" name="enable_graphics_debugging"> 349 <widget class="QCheckBox" name="enable_graphics_debugging">
314 <property name="enabled"> 350 <property name="enabled">
@@ -322,55 +358,26 @@
322 </property> 358 </property>
323 </widget> 359 </widget>
324 </item> 360 </item>
325 <item row="6" column="0"> 361 <item row="5" column="0">
326 <widget class="QCheckBox" name="dump_macros"> 362 <widget class="QCheckBox" name="dump_shaders">
327 <property name="enabled"> 363 <property name="enabled">
328 <bool>true</bool> 364 <bool>true</bool>
329 </property> 365 </property>
330 <property name="toolTip"> 366 <property name="toolTip">
331 <string>When checked, it will dump all the macro programs of the GPU</string> 367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
332 </property> 368 </property>
333 <property name="text"> 369 <property name="text">
334 <string>Dump Maxwell Macros</string> 370 <string>Dump Game Shaders</string>
335 </property> 371 </property>
336 </widget> 372 </widget>
337 </item> 373 </item>
338 <item row="1" column="0"> 374 <item row="1" column="0">
339 <widget class="QCheckBox" name="enable_shader_feedback"> 375 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
340 <property name="toolTip">
341 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
342 </property>
343 <property name="text">
344 <string>Enable Shader Feedback</string>
345 </property>
346 </widget>
347 </item>
348 <item row="2" column="0">
349 <widget class="QCheckBox" name="enable_nsight_aftermath">
350 <property name="toolTip">
351 <string>When checked, it enables Nsight Aftermath crash dumps</string>
352 </property>
353 <property name="text"> 376 <property name="text">
354 <string>Enable Nsight Aftermath</string> 377 <string>Enable Renderdoc Hotkey</string>
355 </property> 378 </property>
356 </widget> 379 </widget>
357 </item> 380 </item>
358 <item row="8" column="0">
359 <spacer name="verticalSpacer_5">
360 <property name="orientation">
361 <enum>Qt::Vertical</enum>
362 </property>
363 <property name="sizeType">
364 <enum>QSizePolicy::Preferred</enum>
365 </property>
366 <property name="sizeHint" stdset="0">
367 <size>
368 <width>20</width>
369 <height>0</height>
370 </size>
371 </property>
372 </spacer>
373 </item>
374 </layout> 381 </layout>
375 </widget> 382 </widget>
376 </item> 383 </item>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 34ab01617..a9fde9f4f 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,6 +4,7 @@
4#include "yuzu/configuration/configure_ui.h" 4#include "yuzu/configuration/configure_ui.h"
5 5
6#include <array> 6#include <array>
7#include <cstdlib>
7#include <set> 8#include <set>
8#include <stdexcept> 9#include <stdexcept>
9#include <string> 10#include <string>
@@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa
94} 95}
95 96
96static u32 ScreenshotDimensionToInt(const QString& height) { 97static u32 ScreenshotDimensionToInt(const QString& height) {
97 try { 98 return std::strtoul(height.toUtf8(), nullptr, 0);
98 return std::stoi(height.toStdString());
99 } catch (std::invalid_argument&) {
100 return 0;
101 }
102} 99}
103 100
104ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) 101ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index d63093985..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -63,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
63 return tr("%", context.c_str()); 63 return tr("%", context.c_str());
64 } 64 }
65 65
66 return QStringLiteral(""); 66 return default_suffix;
67} 67}
68 68
69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { 69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -71,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
71 71
72 QStyle* style = parent->style(); 72 QStyle* style = parent->style();
73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); 73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); 74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); 75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
77 77
@@ -151,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
151 return -1; 151 return -1;
152 }; 152 };
153 153
154 const u32 setting_value = std::stoi(setting.ToString()); 154 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
155 combobox->setCurrentIndex(find_index(setting_value)); 155 combobox->setCurrentIndex(find_index(setting_value));
156 156
157 serializer = [this, enumeration]() { 157 serializer = [this, enumeration]() {
@@ -160,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
160 }; 160 };
161 161
162 restore_func = [this, find_index]() { 162 restore_func = [this, find_index]() {
163 const u32 global_value = std::stoi(RelevantDefault(setting)); 163 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
164 combobox->setCurrentIndex(find_index(global_value)); 164 combobox->setCurrentIndex(find_index(global_value));
165 }; 165 };
166 166
@@ -209,7 +209,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
209 } 209 }
210 }; 210 };
211 211
212 const u32 setting_value = std::stoi(setting.ToString()); 212 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
213 set_index(setting_value); 213 set_index(setting_value);
214 214
215 serializer = [get_selected]() { 215 serializer = [get_selected]() {
@@ -218,7 +218,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
218 }; 218 };
219 219
220 restore_func = [this, set_index]() { 220 restore_func = [this, set_index]() {
221 const u32 global_value = std::stoi(RelevantDefault(setting)); 221 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
222 set_index(global_value); 222 set_index(global_value);
223 }; 223 };
224 224
@@ -255,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
255 return line_edit; 255 return line_edit;
256} 256}
257 257
258static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
259 QLabel* feedback, const QString& use_format, QSlider* slider,
260 std::function<std::string()>& serializer,
261 std::function<void()>& restore_func) {
262 const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
263
264 const auto update_feedback = [=](int value) {
265 int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
266 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
267 };
268
269 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
270 update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
271
272 slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
273 slider->setMaximum(max_val);
274 slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
275
276 serializer = [slider]() { return std::to_string(slider->value()); };
277 restore_func = [slider, &setting]() {
278 slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
279 };
280}
281
282static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
283 QLabel* feedback, const QString& use_format, QSlider* slider,
284 std::function<std::string()>& serializer,
285 std::function<void()>& restore_func) {
286 const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
287 const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
288 const float use_multiplier =
289 multiplier == default_multiplier ? default_float_multiplier : multiplier;
290
291 const auto update_feedback = [=](float value) {
292 int present = (reversed ? max_val - value : value) + 0.5f;
293 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
294 };
295
296 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
297 update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
298
299 slider->setMinimum(min_val * use_multiplier);
300 slider->setMaximum(max_val * use_multiplier);
301 slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
302
303 serializer = [slider, use_multiplier]() {
304 return std::to_string(slider->value() / use_multiplier);
305 };
306 restore_func = [slider, &setting, use_multiplier]() {
307 slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
308 };
309}
310
258QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, 311QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
259 std::function<std::string()>& serializer, 312 std::function<std::string()>& serializer,
260 std::function<void()>& restore_func, 313 std::function<void()>& restore_func,
@@ -278,27 +331,19 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
278 331
279 layout->setContentsMargins(0, 0, 0, 0); 332 layout->setContentsMargins(0, 0, 0, 0);
280 333
281 int max_val = std::stoi(setting.MaxVal()); 334 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
282
283 QString suffix =
284 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
285 335
286 const QString use_format = QStringLiteral("%1").append(suffix); 336 const QString use_format = QStringLiteral("%1").append(suffix);
287 337
288 QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { 338 if (setting.IsIntegral()) {
289 int present = (reversed ? max_val - value : value) * multiplier + 0.5f; 339 CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
290 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); 340 restore_func);
291 }); 341 } else {
292 342 CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
293 slider->setMinimum(std::stoi(setting.MinVal())); 343 restore_func);
294 slider->setMaximum(max_val); 344 }
295 slider->setValue(std::stoi(setting.ToString()));
296 345
297 slider->setInvertedAppearance(reversed); 346 slider->setInvertedAppearance(reversed);
298 slider->setInvertedControls(reversed);
299
300 serializer = [this]() { return std::to_string(slider->value()); };
301 restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
302 347
303 if (!Settings::IsConfiguringGlobal()) { 348 if (!Settings::IsConfiguringGlobal()) {
304 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); 349 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
@@ -311,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
311 std::function<std::string()>& serializer, 356 std::function<std::string()>& serializer,
312 std::function<void()>& restore_func, 357 std::function<void()>& restore_func,
313 const std::function<void()>& touch) { 358 const std::function<void()>& touch) {
314 const int min_val = 359 const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
315 setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); 360 const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
316 const int max_val = 361 const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
317 setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
318 const int default_val = std::stoi(setting.ToString());
319 362
320 QString suffix = 363 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
321 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
322 364
323 spinbox = new QSpinBox(this); 365 spinbox = new QSpinBox(this);
324 spinbox->setRange(min_val, max_val); 366 spinbox->setRange(min_val, max_val);
@@ -329,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
329 serializer = [this]() { return std::to_string(spinbox->value()); }; 371 serializer = [this]() { return std::to_string(spinbox->value()); };
330 372
331 restore_func = [this]() { 373 restore_func = [this]() {
332 auto value{std::stol(RelevantDefault(setting))}; 374 auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
333 spinbox->setValue(value); 375 spinbox->setValue(value);
334 }; 376 };
335 377
336 if (!Settings::IsConfiguringGlobal()) { 378 if (!Settings::IsConfiguringGlobal()) {
337 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { 379 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
338 if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { 380 if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
339 touch(); 381 touch();
340 } 382 }
341 }); 383 });
@@ -344,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
344 return spinbox; 386 return spinbox;
345} 387}
346 388
389QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
390 std::function<std::string()>& serializer,
391 std::function<void()>& restore_func,
392 const std::function<void()>& touch) {
393 const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
394 const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
395 const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
396
397 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
398
399 double_spinbox = new QDoubleSpinBox(this);
400 double_spinbox->setRange(min_val, max_val);
401 double_spinbox->setValue(default_val);
402 double_spinbox->setSuffix(suffix);
403 double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
404
405 serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
406
407 restore_func = [this]() {
408 auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
409 double_spinbox->setValue(value);
410 };
411
412 if (!Settings::IsConfiguringGlobal()) {
413 QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
414 [this, touch]() {
415 if (double_spinbox->value() !=
416 std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
417 touch();
418 }
419 });
420 }
421
422 return double_spinbox;
423}
424
347QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, 425QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
348 std::function<void()>& restore_func, 426 std::function<void()>& restore_func,
349 const std::function<void()>& touch) { 427 const std::function<void()>& touch) {
@@ -353,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
353 } 431 }
354 432
355 auto to_hex = [=](const std::string& input) { 433 auto to_hex = [=](const std::string& input) {
356 return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); 434 return QString::fromStdString(
435 fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
357 }; 436 };
358 437
359 QRegularExpressionValidator* regex = new QRegularExpressionValidator( 438 QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -366,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
366 line_edit->setValidator(regex); 445 line_edit->setValidator(regex);
367 446
368 auto hex_to_dec = [this]() -> std::string { 447 auto hex_to_dec = [this]() -> std::string {
369 return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); 448 return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
370 }; 449 };
371 450
372 serializer = [hex_to_dec]() { return hex_to_dec(); }; 451 serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -386,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
386 std::function<void()>& restore_func, 465 std::function<void()>& restore_func,
387 const std::function<void()>& touch) { 466 const std::function<void()>& touch) {
388 const long long current_time = QDateTime::currentSecsSinceEpoch(); 467 const long long current_time = QDateTime::currentSecsSinceEpoch();
389 const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); 468 const s64 the_time =
469 disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
390 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); 470 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
391 471
392 date_time_edit = new QDateTimeEdit(this); 472 date_time_edit = new QDateTimeEdit(this);
@@ -399,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
399 auto get_clear_val = [this, restrict, current_time]() { 479 auto get_clear_val = [this, restrict, current_time]() {
400 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { 480 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
401 if (restrict && checkbox->checkState() == Qt::Checked) { 481 if (restrict && checkbox->checkState() == Qt::Checked) {
402 return std::stoll(RelevantDefault(setting)); 482 return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
403 } 483 }
404 return current_time; 484 return current_time;
405 }()); 485 }());
@@ -506,8 +586,7 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
506 } else { 586 } else {
507 data_component = CreateCombobox(serializer, restore_func, touch); 587 data_component = CreateCombobox(serializer, restore_func, touch);
508 } 588 }
509 } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || 589 } else if (setting.IsIntegral()) {
510 type == typeid(s64) || type == typeid(u8)) {
511 switch (request) { 590 switch (request) {
512 case RequestType::Slider: 591 case RequestType::Slider:
513 case RequestType::ReverseSlider: 592 case RequestType::ReverseSlider:
@@ -534,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
534 default: 613 default:
535 UNIMPLEMENTED(); 614 UNIMPLEMENTED();
536 } 615 }
616 } else if (setting.IsFloatingPoint()) {
617 switch (request) {
618 case RequestType::Default:
619 case RequestType::SpinBox:
620 data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
621 break;
622 case RequestType::Slider:
623 case RequestType::ReverseSlider:
624 data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
625 serializer, restore_func, touch);
626 break;
627 default:
628 UNIMPLEMENTED();
629 }
537 } else if (type == typeid(std::string)) { 630 } else if (type == typeid(std::string)) {
538 switch (request) { 631 switch (request) {
539 case RequestType::Default: 632 case RequestType::Default:
@@ -638,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
638 return std::pair{translations.at(id).first, translations.at(id).second}; 731 return std::pair{translations.at(id).first, translations.at(id).second};
639 } 732 }
640 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); 733 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
641 return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; 734 return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
642 }(); 735 }();
643 736
644 if (label == QStringLiteral("")) { 737 if (label == QStringLiteral()) {
645 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", 738 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
646 setting.GetLabel()); 739 setting.GetLabel());
647 return; 740 return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index 5303dd898..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,7 @@ class QObject;
22class QPushButton; 22class QPushButton;
23class QSlider; 23class QSlider;
24class QSpinBox; 24class QSpinBox;
25class QDoubleSpinBox;
25class QRadioButton; 26class QRadioButton;
26 27
27namespace Settings { 28namespace Settings {
@@ -43,6 +44,10 @@ enum class RequestType {
43 MaxEnum, 44 MaxEnum,
44}; 45};
45 46
47constexpr float default_multiplier{1.f};
48constexpr float default_float_multiplier{100.f};
49static const QString default_suffix = QStringLiteral();
50
46class Widget : public QWidget { 51class Widget : public QWidget {
47 Q_OBJECT 52 Q_OBJECT
48 53
@@ -66,8 +71,9 @@ public:
66 const ComboboxTranslationMap& combobox_translations, QWidget* parent, 71 const ComboboxTranslationMap& combobox_translations, QWidget* parent,
67 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, 72 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
68 RequestType request = RequestType::Default, bool managed = true, 73 RequestType request = RequestType::Default, bool managed = true,
69 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 74 float multiplier = default_multiplier,
70 const QString& suffix = QStringLiteral("")); 75 Settings::BasicSetting* other_setting = nullptr,
76 const QString& suffix = default_suffix);
71 virtual ~Widget(); 77 virtual ~Widget();
72 78
73 /** 79 /**
@@ -89,6 +95,7 @@ public:
89 QPushButton* restore_button{}; ///< Restore button for custom configurations 95 QPushButton* restore_button{}; ///< Restore button for custom configurations
90 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit 96 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
91 QSpinBox* spinbox{}; 97 QSpinBox* spinbox{};
98 QDoubleSpinBox* double_spinbox{};
92 QCheckBox* checkbox{}; 99 QCheckBox* checkbox{};
93 QSlider* slider{}; 100 QSlider* slider{};
94 QComboBox* combobox{}; 101 QComboBox* combobox{};
@@ -126,6 +133,9 @@ private:
126 const std::function<void()>& touch); 133 const std::function<void()>& touch);
127 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, 134 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
128 std::function<void()>& restore_func, const std::function<void()>& touch); 135 std::function<void()>& restore_func, const std::function<void()>& touch);
136 QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
137 std::function<void()>& restore_func,
138 const std::function<void()>& touch);
129 139
130 QWidget* parent; 140 QWidget* parent;
131 const TranslationMap& translations; 141 const TranslationMap& translations;
@@ -145,14 +155,15 @@ public:
145 Widget* BuildWidget(Settings::BasicSetting* setting, 155 Widget* BuildWidget(Settings::BasicSetting* setting,
146 std::vector<std::function<void(bool)>>& apply_funcs, 156 std::vector<std::function<void(bool)>>& apply_funcs,
147 RequestType request = RequestType::Default, bool managed = true, 157 RequestType request = RequestType::Default, bool managed = true,
148 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 158 float multiplier = default_multiplier,
149 const QString& suffix = QStringLiteral("")) const; 159 Settings::BasicSetting* other_setting = nullptr,
160 const QString& suffix = default_suffix) const;
150 161
151 Widget* BuildWidget(Settings::BasicSetting* setting, 162 Widget* BuildWidget(Settings::BasicSetting* setting,
152 std::vector<std::function<void(bool)>>& apply_funcs, 163 std::vector<std::function<void(bool)>>& apply_funcs,
153 Settings::BasicSetting* other_setting, 164 Settings::BasicSetting* other_setting,
154 RequestType request = RequestType::Default, 165 RequestType request = RequestType::Default,
155 const QString& suffix = QStringLiteral("")) const; 166 const QString& suffix = default_suffix) const;
156 167
157 const ComboboxTranslationMap& ComboboxTranslations() const; 168 const ComboboxTranslationMap& ComboboxTranslations() const;
158 169
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <QKeySequence>
8#include <QString>
9#include <QWidget>
7#include "core/hid/hid_types.h" 10#include "core/hid/hid_types.h"
8 11
9class QDialog; 12class QDialog;
10class QKeySequence;
11class QSettings; 13class QSettings;
12class QShortcut; 14class QShortcut;
13class ControllerShortcut; 15class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 97d216638..adb7b332f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,6 +9,7 @@
9#include <memory> 9#include <memory>
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h" 11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h"
12#ifdef __APPLE__ 13#ifdef __APPLE__
13#include <unistd.h> // for chdir 14#include <unistd.h> // for chdir
14#endif 15#endif
@@ -1348,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
1348 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1349 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1349 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1350 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
1350 }); 1351 });
1352 connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
1353 if (Settings::values.enable_renderdoc_hotkey) {
1354 system->GetRenderdocAPI().ToggleCapture();
1355 }
1356 });
1351 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1357 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1352 if (Settings::values.mouse_enabled) { 1358 if (Settings::values.mouse_enabled) {
1353 Settings::values.mouse_panning = false; 1359 Settings::values.mouse_panning = false;
@@ -1545,6 +1551,7 @@ void GMainWindow::ConnectMenuEvents() {
1545 // Tools 1551 // Tools
1546 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1547 ReinitializeKeyBehavior::Warning)); 1553 ReinitializeKeyBehavior::Warning));
1554 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1548 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1555 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1549 1556
1550 // TAS 1557 // TAS
@@ -1584,6 +1591,8 @@ void GMainWindow::UpdateMenuState() {
1584 } 1591 }
1585 1592
1586 multiplayer_state->UpdateNotificationStatus(); 1593 multiplayer_state->UpdateNotificationStatus();
1594
1595 ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence());
1587} 1596}
1588 1597
1589void GMainWindow::OnDisplayTitleBars(bool show) { 1598void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -3104,10 +3113,9 @@ void GMainWindow::OnMenuInstallToNAND() {
3104 QFuture<InstallResult> future; 3113 QFuture<InstallResult> future;
3105 InstallResult result; 3114 InstallResult result;
3106 3115
3107 if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || 3116 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3108 file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3109 3117
3110 future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); 3118 future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
3111 3119
3112 while (!future.isFinished()) { 3120 while (!future.isFinished()) {
3113 QCoreApplication::processEvents(); 3121 QCoreApplication::processEvents();
@@ -3166,7 +3174,7 @@ void GMainWindow::OnMenuInstallToNAND() {
3166 ui->action_Install_File_NAND->setEnabled(true); 3174 ui->action_Install_File_NAND->setEnabled(true);
3167} 3175}
3168 3176
3169InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { 3177InstallResult GMainWindow::InstallNSP(const QString& filename) {
3170 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, 3178 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3171 const FileSys::VirtualFile& dest, std::size_t block_size) { 3179 const FileSys::VirtualFile& dest, std::size_t block_size) {
3172 if (src == nullptr || dest == nullptr) { 3180 if (src == nullptr || dest == nullptr) {
@@ -3200,9 +3208,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3200 return InstallResult::Failure; 3208 return InstallResult::Failure;
3201 } 3209 }
3202 } else { 3210 } else {
3203 const auto xci = std::make_shared<FileSys::XCI>( 3211 return InstallResult::Failure;
3204 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3205 nsp = xci->GetSecurePartitionNSP();
3206 } 3212 }
3207 3213
3208 if (nsp->GetStatus() != Loader::ResultStatus::Success) { 3214 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -4128,6 +4134,27 @@ void GMainWindow::OnToggleStatusBar() {
4128 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4129} 4135}
4130 4136
4137void GMainWindow::OnMiiEdit() {
4138 constexpr u64 MiiEditId = 0x0100000000001009ull;
4139 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4140 if (!bis_system) {
4141 QMessageBox::warning(this, tr("No firmware available"),
4142 tr("Please install the firmware to use the Mii editor."));
4143 return;
4144 }
4145
4146 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4147 if (!mii_applet_nca) {
4148 QMessageBox::warning(this, tr("Mii Edit Applet"),
4149 tr("Mii editor is not available. Please reinstall firmware."));
4150 return;
4151 }
4152
4153 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
4154 UISettings::values.roms_path = QFileInfo(filename).path();
4155 BootGame(filename);
4156}
4157
4131void GMainWindow::OnCaptureScreenshot() { 4158void GMainWindow::OnCaptureScreenshot() {
4132 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4159 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4133 return; 4160 return;
@@ -4534,6 +4561,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4534 if (behavior == ReinitializeKeyBehavior::Warning) { 4561 if (behavior == ReinitializeKeyBehavior::Warning) {
4535 game_list->PopulateAsync(UISettings::values.game_dirs); 4562 game_list->PopulateAsync(UISettings::values.game_dirs);
4536 } 4563 }
4564
4565 UpdateMenuState();
4537} 4566}
4538 4567
4539bool GMainWindow::CheckSystemArchiveDecryption() { 4568bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4555,6 +4584,22 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4555 return mii_nca->GetRomFS().get() != nullptr; 4584 return mii_nca->GetRomFS().get() != nullptr;
4556} 4585}
4557 4586
4587bool GMainWindow::CheckFirmwarePresence() {
4588 constexpr u64 MiiEditId = 0x0100000000001009ull;
4589
4590 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4591 if (!bis_system) {
4592 return false;
4593 }
4594
4595 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4596 if (!mii_applet_nca) {
4597 return false;
4598 }
4599
4600 return true;
4601}
4602
4558bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4603bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4559 u64* selected_title_id, u8* selected_content_record_type) { 4604 u64* selected_title_id, u8* selected_content_record_type) {
4560 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; 4605 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index cf191f698..ba318eb11 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -365,6 +365,7 @@ private slots:
365 void ResetWindowSize720(); 365 void ResetWindowSize720();
366 void ResetWindowSize900(); 366 void ResetWindowSize900();
367 void ResetWindowSize1080(); 367 void ResetWindowSize1080();
368 void OnMiiEdit();
368 void OnCaptureScreenshot(); 369 void OnCaptureScreenshot();
369 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 370 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
370 void OnLanguageChanged(const QString& locale); 371 void OnLanguageChanged(const QString& locale);
@@ -386,7 +387,7 @@ private:
386 void RemoveCacheStorage(u64 program_id); 387 void RemoveCacheStorage(u64 program_id);
387 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 388 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
388 u64* selected_title_id, u8* selected_content_record_type); 389 u64* selected_title_id, u8* selected_content_record_type);
389 InstallResult InstallNSPXCI(const QString& filename); 390 InstallResult InstallNSP(const QString& filename);
390 InstallResult InstallNCA(const QString& filename); 391 InstallResult InstallNCA(const QString& filename);
391 void MigrateConfigFiles(); 392 void MigrateConfigFiles();
392 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 393 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -409,6 +410,7 @@ private:
409 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 410 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
410 bool CheckDarkMode(); 411 bool CheckDarkMode();
411 bool CheckSystemArchiveDecryption(); 412 bool CheckSystemArchiveDecryption();
413 bool CheckFirmwarePresence();
412 void ConfigureFilesystemProvider(const std::string& filepath); 414 void ConfigureFilesystemProvider(const std::string& filepath);
413 415
414 QString GetTasStateDescription() const; 416 QString GetTasStateDescription() const;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index e54d7d75d..91d6c5ef3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -150,6 +150,8 @@
150 <addaction name="action_Rederive"/> 150 <addaction name="action_Rederive"/>
151 <addaction name="action_Verify_installed_contents"/> 151 <addaction name="action_Verify_installed_contents"/>
152 <addaction name="separator"/> 152 <addaction name="separator"/>
153 <addaction name="action_Load_Mii_Edit"/>
154 <addaction name="separator"/>
153 <addaction name="action_Capture_Screenshot"/> 155 <addaction name="action_Capture_Screenshot"/>
154 <addaction name="menuTAS"/> 156 <addaction name="menuTAS"/>
155 </widget> 157 </widget>
@@ -217,7 +219,7 @@
217 </action> 219 </action>
218 <action name="action_Verify_installed_contents"> 220 <action name="action_Verify_installed_contents">
219 <property name="text"> 221 <property name="text">
220 <string>Verify installed contents</string> 222 <string>&amp;Verify Installed Contents</string>
221 </property> 223 </property>
222 </action> 224 </action>
223 <action name="action_About"> 225 <action name="action_About">
@@ -368,6 +370,11 @@
368 <string>&amp;Capture Screenshot</string> 370 <string>&amp;Capture Screenshot</string>
369 </property> 371 </property>
370 </action> 372 </action>
373 <action name="action_Load_Mii_Edit">
374 <property name="text">
375 <string>Open &amp;Mii Editor</string>
376 </property>
377 </action>
371 <action name="action_Configure_Tas"> 378 <action name="action_Configure_Tas">
372 <property name="text"> 379 <property name="text">
373 <string>&amp;Configure TAS...</string> 380 <string>&amp;Configure TAS...</string>
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
259 std::stringstream ss(title_list); 259 std::stringstream ss(title_list);
260 std::string line; 260 std::string line;
261 while (std::getline(ss, line, '|')) { 261 while (std::getline(ss, line, '|')) {
262 const auto title_id = std::stoul(line, nullptr, 16); 262 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); 263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
264 264
265 std::stringstream inner_ss(disabled_list); 265 std::stringstream inner_ss(disabled_list);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 55d0938f7..087cfaa26 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -264,8 +264,9 @@ int main(int argc, char** argv) {
264 nickname = match[1]; 264 nickname = match[1];
265 password = match[2]; 265 password = match[2];
266 address = match[3]; 266 address = match[3];
267 if (!match[4].str().empty()) 267 if (!match[4].str().empty()) {
268 port = static_cast<u16>(std::stoi(match[4])); 268 port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
269 }
269 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); 270 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
270 if (!std::regex_match(nickname, nickname_re)) { 271 if (!std::regex_match(nickname, nickname_re)) {
271 std::cout 272 std::cout