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.kts2
-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.kt71
-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/emu_window/emu_window.cpp14
-rw-r--r--src/android/app/src/main/jni/native.cpp37
-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/renderer/command/command_processing_time_estimator.cpp4
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h2
-rw-r--r--src/audio_core/renderer/system.cpp10
-rw-r--r--src/common/CMakeLists.txt8
-rw-r--r--src/common/arm64/native_clock.cpp72
-rw-r--r--src/common/arm64/native_clock.h47
-rw-r--r--src/common/fs/fs_paths.h2
-rw-r--r--src/common/fs/path_util.cpp2
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h4
-rw-r--r--src/common/settings_setting.h2
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h1
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/core/CMakeLists.txt16
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core_timing.cpp79
-rw-r--r--src/core/core_timing.h12
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/program_metadata.cpp8
-rw-r--r--src/core/file_sys/program_metadata.h1
-rw-r--r--src/core/file_sys/vfs.h2
-rw-r--r--src/core/hid/emulated_controller.cpp11
-rw-r--r--src/core/hid/emulated_controller.h2
-rw-r--r--src/core/hle/kernel/k_hardware_timer.cpp4
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp2
-rw-r--r--src/core/hle/kernel/k_page_table.cpp29
-rw-r--r--src/core/hle/kernel/k_page_table.h3
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp89
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h14
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/service/am/am.cpp482
-rw-r--r--src/core/hle/service/am/am.h70
-rw-r--r--src/core/hle/service/am/applet_ae.cpp68
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.h11
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp5
-rw-r--r--src/core/hle/service/am/applets/applet_general_backend.cpp6
-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/am/applets/applets.cpp16
-rw-r--r--src/core/hle/service/am/applets/applets.h49
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp20
-rw-r--r--src/core/hle/service/caps/caps.cpp21
-rw-r--r--src/core/hle/service/caps/caps.h81
-rw-r--r--src/core/hle/service/caps/caps_a.cpp239
-rw-r--r--src/core/hle/service/caps/caps_a.h21
-rw-r--r--src/core/hle/service/caps/caps_c.cpp50
-rw-r--r--src/core/hle/service/caps/caps_c.h10
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp342
-rw-r--r--src/core/hle/service/caps/caps_manager.h72
-rw-r--r--src/core/hle/service/caps/caps_result.h35
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp5
-rw-r--r--src/core/hle/service/caps/caps_sc.h6
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp5
-rw-r--r--src/core/hle/service/caps/caps_ss.h6
-rw-r--r--src/core/hle/service/caps/caps_su.cpp9
-rw-r--r--src/core/hle/service/caps/caps_su.h6
-rw-r--r--src/core/hle/service/caps/caps_types.h184
-rw-r--r--src/core/hle/service/caps/caps_u.cpp104
-rw-r--r--src/core/hle/service/caps/caps_u.h12
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp68
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp1
-rw-r--r--src/core/hle/service/ldn/ldn.cpp91
-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.h13
-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.cpp15
-rw-r--r--src/core/hle/service/nifm/nifm.cpp12
-rw-r--r--src/core/hle/service/nifm/nifm.h1
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp17
-rw-r--r--src/core/hle/service/ns/ns.cpp27
-rw-r--r--src/core/hle/service/ns/ns.h4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h14
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_slot.h2
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp351
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.h65
-rw-r--r--src/core/hle/service/nvnflinger/graphic_buffer_producer.h2
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp11
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h9
-rw-r--r--src/core/hle/service/nvnflinger/ui/fence.h3
-rw-r--r--src/core/hle/service/nvnflinger/ui/graphic_buffer.h4
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp9
-rw-r--r--src/core/hle/service/vi/vi.cpp129
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp2
-rw-r--r--src/core/tools/renderdoc.cpp2
-rw-r--r--src/input_common/drivers/virtual_gamepad.h2
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h2
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp2
-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/dma_pusher.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.txt7
-rw-r--r--src/video_core/host_shaders/convert_d32f_to_abgr8.frag14
-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.h105
-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_base.h3
-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_opengl/maxwell_to_gl.h1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp13
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp7
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp1
-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_present_manager.cpp67
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp1596
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h106
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp138
-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_staging_buffer_pool.cpp33
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp131
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h7
-rw-r--r--src/video_core/surface.cpp3
-rw-r--r--src/video_core/surface.h4
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp8
-rw-r--r--src/video_core/texture_cache/formatter.h2
-rw-r--r--src/video_core/texture_cache/image_base.h2
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp1
-rw-r--r--src/video_core/texture_cache/util.cpp2
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp79
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h87
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp21
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h43
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_ui.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.ui7
-rw-r--r--src/yuzu/configuration/shared_translation.cpp3
-rw-r--r--src/yuzu/game_list.cpp18
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp23
-rw-r--r--src/yuzu/game_list_worker.h6
-rw-r--r--src/yuzu/main.cpp262
-rw-r--r--src/yuzu/main.h17
-rw-r--r--src/yuzu/main.ui45
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
-rw-r--r--src/yuzu/uisettings.h5
-rw-r--r--src/yuzu/util/util.cpp77
-rw-r--r--src/yuzu/util/util.h14
246 files changed, 10611 insertions, 1751 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 431f899b3..84a3308b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -214,7 +214,7 @@ dependencies {
214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
215 implementation("io.coil-kt:coil:2.2.2") 215 implementation("io.coil-kt:coil:2.2.2")
216 implementation("androidx.core:core-splashscreen:1.0.1") 216 implementation("androidx.core:core-splashscreen:1.0.1")
217 implementation("androidx.window:window:1.1.0") 217 implementation("androidx.window:window:1.2.0-beta03")
218 implementation("org.ini4j:ini4j:0.5.4") 218 implementation("org.ini4j:ini4j:0.5.4")
219 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 219 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
220 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 18bc34b9f..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
@@ -9,6 +9,7 @@ import android.view.LayoutInflater
9import android.view.View 9import android.view.View
10import android.view.ViewGroup 10import android.view.ViewGroup
11import android.widget.Toast 11import android.widget.Toast
12import androidx.appcompat.app.AlertDialog
12import androidx.appcompat.app.AppCompatActivity 13import androidx.appcompat.app.AppCompatActivity
13import androidx.fragment.app.DialogFragment 14import androidx.fragment.app.DialogFragment
14import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
@@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle 19import androidx.lifecycle.repeatOnLifecycle
19import com.google.android.material.dialog.MaterialAlertDialogBuilder 20import com.google.android.material.dialog.MaterialAlertDialogBuilder
20import kotlinx.coroutines.launch 21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 23import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
22import org.yuzu.yuzu_emu.model.TaskViewModel 24import org.yuzu.yuzu_emu.model.TaskViewModel
23 25
@@ -28,19 +30,25 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 30
29 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 31 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
30 val titleId = requireArguments().getInt(TITLE) 32 val titleId = requireArguments().getInt(TITLE)
33 val cancellable = requireArguments().getBoolean(CANCELLABLE)
31 34
32 binding = DialogProgressBarBinding.inflate(layoutInflater) 35 binding = DialogProgressBarBinding.inflate(layoutInflater)
33 binding.progressBar.isIndeterminate = true 36 binding.progressBar.isIndeterminate = true
34 val dialog = MaterialAlertDialogBuilder(requireContext()) 37 val dialog = MaterialAlertDialogBuilder(requireContext())
35 .setTitle(titleId) 38 .setTitle(titleId)
36 .setView(binding.root) 39 .setView(binding.root)
37 .create() 40
38 dialog.setCanceledOnTouchOutside(false) 41 if (cancellable) {
42 dialog.setNegativeButton(android.R.string.cancel, null)
43 }
44
45 val alertDialog = dialog.create()
46 alertDialog.setCanceledOnTouchOutside(false)
39 47
40 if (!taskViewModel.isRunning.value) { 48 if (!taskViewModel.isRunning.value) {
41 taskViewModel.runTask() 49 taskViewModel.runTask()
42 } 50 }
43 return dialog 51 return alertDialog
44 } 52 }
45 53
46 override fun onCreateView( 54 override fun onCreateView(
@@ -53,24 +61,50 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
53 61
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 super.onViewCreated(view, savedInstanceState) 63 super.onViewCreated(view, savedInstanceState)
56 viewLifecycleOwner.lifecycleScope.launch { 64 viewLifecycleOwner.lifecycleScope.apply {
57 repeatOnLifecycle(Lifecycle.State.CREATED) { 65 launch {
58 taskViewModel.isComplete.collect { 66 repeatOnLifecycle(Lifecycle.State.CREATED) {
59 if (it) { 67 taskViewModel.isComplete.collect {
60 dismiss() 68 if (it) {
61 when (val result = taskViewModel.result.value) { 69 dismiss()
62 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) 70 when (val result = taskViewModel.result.value) {
63 .show() 71 is String -> Toast.makeText(
64 72 requireContext(),
65 is MessageDialogFragment -> result.show( 73 result,
66 requireActivity().supportFragmentManager, 74 Toast.LENGTH_LONG
67 MessageDialogFragment.TAG 75 ).show()
68 ) 76
77 is MessageDialogFragment -> result.show(
78 requireActivity().supportFragmentManager,
79 MessageDialogFragment.TAG
80 )
81 }
82 taskViewModel.clear()
69 } 83 }
70 taskViewModel.clear()
71 } 84 }
72 } 85 }
73 } 86 }
87 launch {
88 repeatOnLifecycle(Lifecycle.State.CREATED) {
89 taskViewModel.cancelled.collect {
90 if (it) {
91 dialog?.setTitle(R.string.cancelling)
92 }
93 }
94 }
95 }
96 }
97 }
98
99 // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
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)
74 } 108 }
75 } 109 }
76 110
@@ -78,16 +112,19 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
78 const val TAG = "IndeterminateProgressDialogFragment" 112 const val TAG = "IndeterminateProgressDialogFragment"
79 113
80 private const val TITLE = "Title" 114 private const val TITLE = "Title"
115 private const val CANCELLABLE = "Cancellable"
81 116
82 fun newInstance( 117 fun newInstance(
83 activity: AppCompatActivity, 118 activity: AppCompatActivity,
84 titleId: Int, 119 titleId: Int,
120 cancellable: Boolean = false,
85 task: () -> Any 121 task: () -> Any
86 ): IndeterminateProgressDialogFragment { 122 ): IndeterminateProgressDialogFragment {
87 val dialog = IndeterminateProgressDialogFragment() 123 val dialog = IndeterminateProgressDialogFragment()
88 val args = Bundle() 124 val args = Bundle()
89 ViewModelProvider(activity)[TaskViewModel::class.java].task = task 125 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
90 args.putInt(TITLE, titleId) 126 args.putInt(TITLE, titleId)
127 args.putBoolean(CANCELLABLE, cancellable)
91 dialog.arguments = args 128 dialog.arguments = args
92 return dialog 129 return dialog
93 } 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/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..598f4e8bf 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 }
@@ -220,7 +218,6 @@ public:
220 return; 218 return;
221 } 219 }
222 m_window->OnSurfaceChanged(m_native_window); 220 m_window->OnSurfaceChanged(m_native_window);
223 m_system.Renderer().NotifySurfaceChanged();
224 } 221 }
225 222
226 void ConfigureFilesystemProvider(const std::string& filepath) { 223 void ConfigureFilesystemProvider(const std::string& filepath) {
@@ -607,8 +604,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
607} 604}
608 605
609int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, 606int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
610 [[maybe_unused]] jstring j_file) { 607 jstring j_file,
611 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); 608 jstring j_file_extension) {
609 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
610 GetJString(env, j_file_extension));
612} 611}
613 612
614void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, 613void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
@@ -879,4 +878,24 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
879 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); 878 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
880} 879}
881 880
881void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* env,
882 jobject instance) {
883 const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
884 auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
885 Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
886
887 Service::Account::ProfileManager manager;
888 const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
889 ASSERT(user_id);
890
891 const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
892 EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
893 FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
894
895 const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
896 if (!Common::FS::CreateParentDirs(full_path)) {
897 LOG_WARNING(Frontend, "Failed to create full path of the default user's save directory");
898 }
899}
900
882} // extern "C" 901} // 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/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/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
index 161a94461..a0cc228f7 100644
--- a/src/audio_core/renderer/command/mix/depop_prepare.h
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -16,7 +16,7 @@ namespace AudioCore::Renderer {
16 16
17/** 17/**
18 * AudioRenderer command for preparing depop. 18 * AudioRenderer command for preparing depop.
19 * Adds the previusly output last samples to the depop buffer. 19 * Adds the previously output last samples to the depop buffer.
20 */ 20 */
21struct DepopPrepareCommand : ICommand { 21struct DepopPrepareCommand : ICommand {
22 /** 22 /**
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 416203c59..8a1861051 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64)
189 target_link_libraries(common PRIVATE xbyak::xbyak) 189 target_link_libraries(common PRIVATE xbyak::xbyak)
190endif() 190endif()
191 191
192if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
193 target_sources(common
194 PRIVATE
195 arm64/native_clock.cpp
196 arm64/native_clock.h
197 )
198endif()
199
192if (MSVC) 200if (MSVC)
193 target_compile_definitions(common PRIVATE 201 target_compile_definitions(common PRIVATE
194 # The standard library doesn't provide any replacement for codecvt yet 202 # The standard library doesn't provide any replacement for codecvt yet
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
new file mode 100644
index 000000000..88fdba527
--- /dev/null
+++ b/src/common/arm64/native_clock.cpp
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/arm64/native_clock.h"
5
6namespace Common::Arm64 {
7
8namespace {
9
10NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
11 return (static_cast<NativeClock::FactorType>(num) << 64) / den;
12}
13
14u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
15 return static_cast<u64>((m * factor) >> 64);
16}
17
18} // namespace
19
20NativeClock::NativeClock() {
21 const u64 host_cntfrq = GetHostCNTFRQ();
22 ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
23 us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
24 ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
25 guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
26 gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
27}
28
29std::chrono::nanoseconds NativeClock::GetTimeNS() const {
30 return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
31}
32
33std::chrono::microseconds NativeClock::GetTimeUS() const {
34 return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
35}
36
37std::chrono::milliseconds NativeClock::GetTimeMS() const {
38 return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
39}
40
41u64 NativeClock::GetCNTPCT() const {
42 return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
43}
44
45u64 NativeClock::GetGPUTick() const {
46 return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
47}
48
49u64 NativeClock::GetHostTicksNow() const {
50 u64 cntvct_el0 = 0;
51 asm volatile("dsb ish\n\t"
52 "mrs %[cntvct_el0], cntvct_el0\n\t"
53 "dsb ish\n\t"
54 : [cntvct_el0] "=r"(cntvct_el0));
55 return cntvct_el0;
56}
57
58u64 NativeClock::GetHostTicksElapsed() const {
59 return GetHostTicksNow();
60}
61
62bool NativeClock::IsNative() const {
63 return true;
64}
65
66u64 NativeClock::GetHostCNTFRQ() {
67 u64 cntfrq_el0 = 0;
68 asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
69 return cntfrq_el0;
70}
71
72} // namespace Common::Arm64
diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h
new file mode 100644
index 000000000..a28b419f2
--- /dev/null
+++ b/src/common/arm64/native_clock.h
@@ -0,0 +1,47 @@
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/wall_clock.h"
7
8namespace Common::Arm64 {
9
10class NativeClock final : public WallClock {
11public:
12 explicit NativeClock();
13
14 std::chrono::nanoseconds GetTimeNS() const override;
15
16 std::chrono::microseconds GetTimeUS() const override;
17
18 std::chrono::milliseconds GetTimeMS() const override;
19
20 u64 GetCNTPCT() const override;
21
22 u64 GetGPUTick() const override;
23
24 u64 GetHostTicksNow() const override;
25
26 u64 GetHostTicksElapsed() const override;
27
28 bool IsNative() const override;
29
30 static u64 GetHostCNTFRQ();
31
32public:
33 using FactorType = unsigned __int128;
34
35 FactorType GetGuestCNTFRQFactor() const {
36 return guest_cntfrq_factor;
37 }
38
39private:
40 FactorType ns_cntfrq_factor;
41 FactorType us_cntfrq_factor;
42 FactorType ms_cntfrq_factor;
43 FactorType guest_cntfrq_factor;
44 FactorType gputick_cntfrq_factor;
45};
46
47} // namespace Common::Arm64
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 61bac9eba..441c8af97 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -18,10 +18,12 @@
18#define LOAD_DIR "load" 18#define LOAD_DIR "load"
19#define LOG_DIR "log" 19#define LOG_DIR "log"
20#define NAND_DIR "nand" 20#define NAND_DIR "nand"
21#define PLAY_TIME_DIR "play_time"
21#define SCREENSHOTS_DIR "screenshots" 22#define SCREENSHOTS_DIR "screenshots"
22#define SDMC_DIR "sdmc" 23#define SDMC_DIR "sdmc"
23#define SHADER_DIR "shader" 24#define SHADER_DIR "shader"
24#define TAS_DIR "tas" 25#define TAS_DIR "tas"
26#define ICONS_DIR "icons"
25 27
26// yuzu-specific files 28// yuzu-specific files
27 29
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index dce219fcf..0abd81a45 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -124,10 +124,12 @@ public:
124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); 124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); 125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); 126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
127 GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
127 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); 128 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
128 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
129 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
130 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
132 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
131 } 133 }
132 134
133private: 135private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..63801c924 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -20,10 +20,12 @@ enum class YuzuPath {
20 LoadDir, // Where cheat/mod files are stored. 20 LoadDir, // Where cheat/mod files are stored.
21 LogDir, // Where log files are stored. 21 LogDir, // Where log files are stored.
22 NANDDir, // Where the emulated NAND is stored. 22 NANDDir, // Where the emulated NAND is stored.
23 PlayTimeDir, // Where play time data is stored.
23 ScreenshotsDir, // Where yuzu screenshots are stored. 24 ScreenshotsDir, // Where yuzu screenshots are stored.
24 SDMCDir, // Where the emulated SDMC is stored. 25 SDMCDir, // Where the emulated SDMC is stored.
25 ShaderDir, // Where shaders are stored. 26 ShaderDir, // Where shaders are stored.
26 TASDir, // Where TAS scripts are stored. 27 TASDir, // Where TAS scripts are stored.
28 IconsDir, // Where Icons for Windows shortcuts are stored.
27}; 29};
28 30
29/** 31/**
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 82ec9077e..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};
@@ -350,6 +351,8 @@ struct Values {
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 351 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", 352 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug}; 353 Category::RendererDebug};
354 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
355 bool renderer_amdvlk_depth_bias_workaround{};
353 356
354 // System 357 // System
355 SwitchableSetting<Language, true> language_index{linkage, 358 SwitchableSetting<Language, true> language_index{linkage,
@@ -522,6 +525,7 @@ struct Values {
522 525
523extern Values values; 526extern Values values;
524 527
528void UpdateGPUAccuracy();
525bool IsGPULevelExtreme(); 529bool IsGPULevelExtreme();
526bool IsGPULevelHigh(); 530bool IsGPULevelHigh();
527 531
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index 7be6f26f7..3175ab07d 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -187,6 +187,8 @@ public:
187 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
188 } else if constexpr (std::is_same_v<Type, float>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
189 this->SetValue(std::stof(input)); 189 this->SetValue(std::stof(input));
190 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
191 this->SetValue(ToEnum<AudioEngine>(input));
190 } else { 192 } else {
191 this->SetValue(static_cast<Type>(std::stoll(input))); 193 this->SetValue(static_cast<Type>(std::stoll(input)));
192 } 194 }
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index feab1653d..4c7aba3f5 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
135 return convert.from_bytes(input.data(), input.data() + input.size()); 135 return convert.from_bytes(input.data(), input.data() + input.size());
136} 136}
137 137
138std::u32string UTF8ToUTF32(std::string_view input) {
139 std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
140 return convert.from_bytes(input.data(), input.data() + input.size());
141}
142
138#ifdef _WIN32 143#ifdef _WIN32
139static std::wstring CPToUTF16(u32 code_page, std::string_view input) { 144static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
140 const auto size = 145 const auto size =
diff --git a/src/common/string_util.h b/src/common/string_util.h
index c351f1a0c..9da1ca4e9 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
38 38
39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); 39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); 40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
41[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
41 42
42#ifdef _WIN32 43#ifdef _WIN32
43[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); 44[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 71e15ab4c..caca9a123 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,6 +10,10 @@
10#include "common/x64/rdtsc.h" 10#include "common/x64/rdtsc.h"
11#endif 11#endif
12 12
13#if defined(ARCHITECTURE_arm64) && defined(__linux__)
14#include "common/arm64/native_clock.h"
15#endif
16
13namespace Common { 17namespace Common {
14 18
15class StandardWallClock final : public WallClock { 19class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
53}; 57};
54 58
55std::unique_ptr<WallClock> CreateOptimalClock() { 59std::unique_ptr<WallClock> CreateOptimalClock() {
56#ifdef ARCHITECTURE_x86_64 60#if defined(ARCHITECTURE_x86_64)
57 const auto& caps = GetCPUCaps(); 61 const auto& caps = GetCPUCaps();
58 62
59 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { 63 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
64 // - Is not more precise than 1 GHz (1ns resolution) 68 // - Is not more precise than 1 GHz (1ns resolution)
65 return std::make_unique<StandardWallClock>(); 69 return std::make_unique<StandardWallClock>();
66 } 70 }
71#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
72 return std::make_unique<Arm64::NativeClock>();
67#else 73#else
68 return std::make_unique<StandardWallClock>(); 74 return std::make_unique<StandardWallClock>();
69#endif 75#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b2dc71d4c..e4f499135 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -466,14 +466,18 @@ add_library(core STATIC
466 hle/service/caps/caps_a.h 466 hle/service/caps/caps_a.h
467 hle/service/caps/caps_c.cpp 467 hle/service/caps/caps_c.cpp
468 hle/service/caps/caps_c.h 468 hle/service/caps/caps_c.h
469 hle/service/caps/caps_u.cpp 469 hle/service/caps/caps_manager.cpp
470 hle/service/caps/caps_u.h 470 hle/service/caps/caps_manager.h
471 hle/service/caps/caps_result.h
471 hle/service/caps/caps_sc.cpp 472 hle/service/caps/caps_sc.cpp
472 hle/service/caps/caps_sc.h 473 hle/service/caps/caps_sc.h
473 hle/service/caps/caps_ss.cpp 474 hle/service/caps/caps_ss.cpp
474 hle/service/caps/caps_ss.h 475 hle/service/caps/caps_ss.h
475 hle/service/caps/caps_su.cpp 476 hle/service/caps/caps_su.cpp
476 hle/service/caps/caps_su.h 477 hle/service/caps/caps_su.h
478 hle/service/caps/caps_types.h
479 hle/service/caps/caps_u.cpp
480 hle/service/caps/caps_u.h
477 hle/service/erpt/erpt.cpp 481 hle/service/erpt/erpt.cpp
478 hle/service/erpt/erpt.h 482 hle/service/erpt/erpt.h
479 hle/service/es/es.cpp 483 hle/service/es/es.cpp
@@ -596,6 +600,10 @@ add_library(core STATIC
596 hle/service/mii/types/ver3_store_data.h 600 hle/service/mii/types/ver3_store_data.h
597 hle/service/mii/mii.cpp 601 hle/service/mii/mii.cpp
598 hle/service/mii/mii.h 602 hle/service/mii/mii.h
603 hle/service/mii/mii_database.cpp
604 hle/service/mii/mii_database.h
605 hle/service/mii/mii_database_manager.cpp
606 hle/service/mii/mii_database_manager.h
599 hle/service/mii/mii_manager.cpp 607 hle/service/mii/mii_manager.cpp
600 hle/service/mii/mii_manager.h 608 hle/service/mii/mii_manager.h
601 hle/service/mii/mii_result.h 609 hle/service/mii/mii_result.h
@@ -694,6 +702,8 @@ add_library(core STATIC
694 hle/service/nvnflinger/consumer_base.cpp 702 hle/service/nvnflinger/consumer_base.cpp
695 hle/service/nvnflinger/consumer_base.h 703 hle/service/nvnflinger/consumer_base.h
696 hle/service/nvnflinger/consumer_listener.h 704 hle/service/nvnflinger/consumer_listener.h
705 hle/service/nvnflinger/fb_share_buffer_manager.cpp
706 hle/service/nvnflinger/fb_share_buffer_manager.h
697 hle/service/nvnflinger/graphic_buffer_producer.cpp 707 hle/service/nvnflinger/graphic_buffer_producer.cpp
698 hle/service/nvnflinger/graphic_buffer_producer.h 708 hle/service/nvnflinger/graphic_buffer_producer.h
699 hle/service/nvnflinger/hos_binder_driver_server.cpp 709 hle/service/nvnflinger/hos_binder_driver_server.cpp
@@ -890,7 +900,7 @@ endif()
890create_target_directory_groups(core) 900create_target_directory_groups(core)
891 901
892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 902target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc) 903target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
894if (MINGW) 904if (MINGW)
895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 905 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
896endif() 906endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e8300cd05..0ab2e3b76 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -381,6 +381,10 @@ struct System::Impl {
381 room_member->SendGameInfo(game_info); 381 room_member->SendGameInfo(game_info);
382 } 382 }
383 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
384 status = SystemResultStatus::Success; 388 status = SystemResultStatus::Success;
385 return status; 389 return status;
386 } 390 }
@@ -440,6 +444,9 @@ struct System::Impl {
440 room_member->SendGameInfo(game_info); 444 room_member->SendGameInfo(game_info);
441 } 445 }
442 446
447 // Workarounds
448 Settings::values.renderer_amdvlk_depth_bias_workaround = false;
449
443 LOG_DEBUG(Core, "Shutdown OK"); 450 LOG_DEBUG(Core, "Shutdown OK");
444 } 451 }
445 452
@@ -1071,6 +1078,10 @@ void System::ApplySettings() {
1071 impl->RefreshTime(); 1078 impl->RefreshTime();
1072 1079
1073 if (IsPoweredOn()) { 1080 if (IsPoweredOn()) {
1081 if (Settings::values.custom_rtc_enabled) {
1082 const s64 posix_time{Settings::values.custom_rtc.GetValue()};
1083 GetTimeManager().UpdateLocalSystemClockTime(posix_time);
1084 }
1074 Renderer().RefreshBaseSettings(); 1085 Renderer().RefreshBaseSettings();
1075 } 1086 }
1076} 1087}
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index b98a0cb33..e671b270f 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -32,6 +32,7 @@ struct CoreTiming::Event {
32 std::uintptr_t user_data; 32 std::uintptr_t user_data;
33 std::weak_ptr<EventType> type; 33 std::weak_ptr<EventType> type;
34 s64 reschedule_time; 34 s64 reschedule_time;
35 heap_t::handle_type handle{};
35 36
36 // Sort by time, unless the times are the same, in which case sort by 37 // Sort by time, unless the times are the same, in which case sort by
37 // the order added to the queue 38 // the order added to the queue
@@ -122,9 +123,9 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
122 std::scoped_lock scope{basic_lock}; 123 std::scoped_lock scope{basic_lock};
123 const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future}; 124 const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
124 125
125 event_queue.emplace_back( 126 auto h{event_queue.emplace(
126 Event{next_time.count(), event_fifo_id++, user_data, event_type, 0}); 127 Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
127 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 128 (*h).handle = h;
128 } 129 }
129 130
130 event.Set(); 131 event.Set();
@@ -138,10 +139,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
138 std::scoped_lock scope{basic_lock}; 139 std::scoped_lock scope{basic_lock};
139 const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; 140 const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
140 141
141 event_queue.emplace_back( 142 auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type,
142 Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); 143 resched_time.count()})};
143 144 (*h).handle = h;
144 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
145 } 145 }
146 146
147 event.Set(); 147 event.Set();
@@ -151,15 +151,17 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
151 std::uintptr_t user_data, bool wait) { 151 std::uintptr_t user_data, bool wait) {
152 { 152 {
153 std::scoped_lock lk{basic_lock}; 153 std::scoped_lock lk{basic_lock};
154 const auto itr = 154
155 std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { 155 std::vector<heap_t::handle_type> to_remove;
156 return e.type.lock().get() == event_type.get() && e.user_data == user_data; 156 for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
157 }); 157 const Event& e = *itr;
158 158 if (e.type.lock().get() == event_type.get() && e.user_data == user_data) {
159 // Removing random items breaks the invariant so we have to re-establish it. 159 to_remove.push_back(itr->handle);
160 if (itr != event_queue.end()) { 160 }
161 event_queue.erase(itr, event_queue.end()); 161 }
162 std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 162
163 for (auto h : to_remove) {
164 event_queue.erase(h);
163 } 165 }
164 } 166 }
165 167
@@ -200,35 +202,45 @@ std::optional<s64> CoreTiming::Advance() {
200 std::scoped_lock lock{advance_lock, basic_lock}; 202 std::scoped_lock lock{advance_lock, basic_lock};
201 global_timer = GetGlobalTimeNs().count(); 203 global_timer = GetGlobalTimeNs().count();
202 204
203 while (!event_queue.empty() && event_queue.front().time <= global_timer) { 205 while (!event_queue.empty() && event_queue.top().time <= global_timer) {
204 Event evt = std::move(event_queue.front()); 206 const Event& evt = event_queue.top();
205 std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
206 event_queue.pop_back();
207 207
208 if (const auto event_type{evt.type.lock()}) { 208 if (const auto event_type{evt.type.lock()}) {
209 basic_lock.unlock(); 209 if (evt.reschedule_time == 0) {
210 const auto evt_user_data = evt.user_data;
211 const auto evt_time = evt.time;
212
213 event_queue.pop();
214
215 basic_lock.unlock();
216
217 event_type->callback(
218 evt_user_data, evt_time,
219 std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
220
221 basic_lock.lock();
222 } else {
223 basic_lock.unlock();
210 224
211 const auto new_schedule_time{event_type->callback( 225 const auto new_schedule_time{event_type->callback(
212 evt.user_data, evt.time, 226 evt.user_data, evt.time,
213 std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})}; 227 std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
214 228
215 basic_lock.lock(); 229 basic_lock.lock();
216 230
217 if (evt.reschedule_time != 0) {
218 const auto next_schedule_time{new_schedule_time.has_value() 231 const auto next_schedule_time{new_schedule_time.has_value()
219 ? new_schedule_time.value().count() 232 ? new_schedule_time.value().count()
220 : evt.reschedule_time}; 233 : evt.reschedule_time};
221 234
222 // If this event was scheduled into a pause, its time now is going to be way behind. 235 // If this event was scheduled into a pause, its time now is going to be way
223 // Re-set this event to continue from the end of the pause. 236 // behind. Re-set this event to continue from the end of the pause.
224 auto next_time{evt.time + next_schedule_time}; 237 auto next_time{evt.time + next_schedule_time};
225 if (evt.time < pause_end_time) { 238 if (evt.time < pause_end_time) {
226 next_time = pause_end_time + next_schedule_time; 239 next_time = pause_end_time + next_schedule_time;
227 } 240 }
228 241
229 event_queue.emplace_back( 242 event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data,
230 Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); 243 evt.type, next_schedule_time, evt.handle});
231 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
232 } 244 }
233 } 245 }
234 246
@@ -236,7 +248,7 @@ std::optional<s64> CoreTiming::Advance() {
236 } 248 }
237 249
238 if (!event_queue.empty()) { 250 if (!event_queue.empty()) {
239 return event_queue.front().time; 251 return event_queue.top().time;
240 } else { 252 } else {
241 return std::nullopt; 253 return std::nullopt;
242 } 254 }
@@ -274,7 +286,8 @@ void CoreTiming::ThreadLoop() {
274#endif 286#endif
275 } 287 }
276 } else { 288 } else {
277 // Queue is empty, wait until another event is scheduled and signals us to continue. 289 // Queue is empty, wait until another event is scheduled and signals us to
290 // continue.
278 wait_set = true; 291 wait_set = true;
279 event.Wait(); 292 event.Wait();
280 } 293 }
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index c20e906fb..26a8b93a7 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -11,7 +11,8 @@
11#include <optional> 11#include <optional>
12#include <string> 12#include <string>
13#include <thread> 13#include <thread>
14#include <vector> 14
15#include <boost/heap/fibonacci_heap.hpp>
15 16
16#include "common/common_types.h" 17#include "common/common_types.h"
17#include "common/thread.h" 18#include "common/thread.h"
@@ -151,11 +152,10 @@ private:
151 s64 timer_resolution_ns; 152 s64 timer_resolution_ns;
152#endif 153#endif
153 154
154 // The queue is a min-heap using std::make_heap/push_heap/pop_heap. 155 using heap_t =
155 // We don't use std::priority_queue because we need to be able to serialize, unserialize and 156 boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
156 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't 157
157 // accommodated by the standard adaptor class. 158 heap_t event_queue;
158 std::vector<Event> event_queue;
159 u64 event_fifo_id = 0; 159 u64 event_fifo_id = 0;
160 160
161 std::shared_ptr<EventType> ev_lost; 161 std::shared_ptr<EventType> ev_lost;
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index e55831f27..82964f0a1 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -2,6 +2,8 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <atomic> 4#include <atomic>
5#include <codecvt>
6#include <locale>
5#include <numeric> 7#include <numeric>
6#include <optional> 8#include <optional>
7#include <thread> 9#include <thread>
@@ -12,6 +14,7 @@
12#include "common/logging/log.h" 14#include "common/logging/log.h"
13#include "common/scope_exit.h" 15#include "common/scope_exit.h"
14#include "common/settings.h" 16#include "common/settings.h"
17#include "common/string_util.h"
15#include "core/arm/arm_interface.h" 18#include "core/arm/arm_interface.h"
16#include "core/core.h" 19#include "core/core.h"
17#include "core/debugger/gdbstub.h" 20#include "core/debugger/gdbstub.h"
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
68} 71}
69 72
70static std::string EscapeXML(std::string_view data) { 73static std::string EscapeXML(std::string_view data) {
74 std::u32string converted = U"[Encoding error]";
75 try {
76 converted = Common::UTF8ToUTF32(data);
77 } catch (std::range_error&) {
78 }
79
71 std::string escaped; 80 std::string escaped;
72 escaped.reserve(data.size()); 81 escaped.reserve(data.size());
73 82
74 for (char c : data) { 83 for (char32_t c : converted) {
75 switch (c) { 84 switch (c) {
76 case '&': 85 case '&':
77 escaped += "&amp;"; 86 escaped += "&amp;";
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
86 escaped += "&gt;"; 95 escaped += "&gt;";
87 break; 96 break;
88 default: 97 default:
89 escaped += c; 98 if (c > 0x7f) {
99 escaped += fmt::format("&#{};", static_cast<u32>(c));
100 } else {
101 escaped += static_cast<char>(c);
102 }
90 break; 103 break;
91 } 104 }
92 } 105 }
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/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index f00479bd3..8e291ff67 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -5,6 +5,7 @@
5#include <vector> 5#include <vector>
6 6
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/scope_exit.h"
8#include "core/file_sys/program_metadata.h" 9#include "core/file_sys/program_metadata.h"
9#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 11#include "core/loader/loader.h"
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
95 return Loader::ResultStatus::Success; 96 return Loader::ResultStatus::Success;
96} 97}
97 98
99Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
100 const u64 original_program_id = aci_header.title_id;
101 SCOPE_EXIT({ aci_header.title_id = original_program_id; });
102
103 return this->Load(file);
104}
105
98/*static*/ ProgramMetadata ProgramMetadata::GetDefault() { 106/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
99 // Allow use of cores 0~3 and thread priorities 1~63. 107 // Allow use of cores 0~3 and thread priorities 1~63.
100 constexpr u32 default_thread_info_capability = 0x30007F7; 108 constexpr u32 default_thread_info_capability = 0x30007F7;
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 2e8960b07..9f8e74b13 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -56,6 +56,7 @@ public:
56 static ProgramMetadata GetDefault(); 56 static ProgramMetadata GetDefault();
57 57
58 Loader::ResultStatus Load(VirtualFile file); 58 Loader::ResultStatus Load(VirtualFile file);
59 Loader::ResultStatus Reload(VirtualFile file);
59 60
60 /// Load from parameters instead of NPDM file, used for KIP 61 /// Load from parameters instead of NPDM file, used for KIP
61 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio, 62 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index a93e21f67..a7cd1cae3 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -175,7 +175,7 @@ public:
175 return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset); 175 return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
176 } 176 }
177 177
178 // Renames the file to name. Returns whether or not the operation was successsful. 178 // Renames the file to name. Returns whether or not the operation was successful.
179 virtual bool Rename(std::string_view name) = 0; 179 virtual bool Rename(std::string_view name) = 0;
180 180
181 // Returns the full path of this file as a string, recursively 181 // Returns the full path of this file as a string, recursively
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/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_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index af40092c0..bec714668 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -61,7 +61,7 @@ bool KMemoryRegionTree::Insert(u64 address, size_t size, u32 type_id, u32 new_at
61 found->Reset(address, inserted_region_last, old_pair, new_attr, type_id); 61 found->Reset(address, inserted_region_last, old_pair, new_attr, type_id);
62 this->insert(*found); 62 this->insert(*found);
63 } else { 63 } else {
64 // If we can't re-use, adjust the old region. 64 // If we can't reuse, adjust the old region.
65 found->Reset(old_address, address - 1, old_pair, old_attr, old_type); 65 found->Reset(old_address, address - 1, old_pair, old_attr, old_type);
66 this->insert(*found); 66 this->insert(*found);
67 67
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 9bfc85b34..1fbfbf31f 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -5,6 +5,7 @@
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/literals.h" 6#include "common/literals.h"
7#include "common/scope_exit.h" 7#include "common/scope_exit.h"
8#include "common/settings.h"
8#include "core/core.h" 9#include "core/core.h"
9#include "core/hle/kernel/k_address_space_info.h" 10#include "core/hle/kernel/k_address_space_info.h"
10#include "core/hle/kernel/k_memory_block.h" 11#include "core/hle/kernel/k_memory_block.h"
@@ -337,11 +338,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
337} 338}
338 339
339void KPageTable::Finalize() { 340void KPageTable::Finalize() {
341 auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
342 if (Settings::IsFastmemEnabled()) {
343 m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size);
344 }
345 };
346
340 // Finalize memory blocks. 347 // Finalize memory blocks.
341 m_memory_block_manager.Finalize(m_memory_block_slab_manager, 348 m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
342 [&](KProcessAddress addr, u64 size) {
343 m_memory->UnmapRegion(*m_page_table_impl, addr, size);
344 });
345 349
346 // Release any insecure mapped memory. 350 // Release any insecure mapped memory.
347 if (m_mapped_insecure_memory) { 351 if (m_mapped_insecure_memory) {
@@ -2945,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
2945 KMemoryAttribute::Locked, nullptr)); 2949 KMemoryAttribute::Locked, nullptr));
2946} 2950}
2947 2951
2952Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
2953 KMemoryPermission perm) {
2954 R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
2955 KMemoryState::FlagCanTransfer, KMemoryPermission::All,
2956 KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
2957 KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
2958}
2959
2960Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
2961 const KPageGroup& pg) {
2962 R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
2963 KMemoryState::FlagCanTransfer, KMemoryPermission::None,
2964 KMemoryPermission::None, KMemoryAttribute::All,
2965 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
2966 KMemoryAttribute::Locked, std::addressof(pg)));
2967}
2968
2948Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) { 2969Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
2949 R_RETURN(this->LockMemoryAndOpen( 2970 R_RETURN(this->LockMemoryAndOpen(
2950 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, 2971 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index b9e8c6042..7da675f27 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -104,6 +104,9 @@ public:
104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state); 104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state); 105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
106 106
107 Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
108 KMemoryPermission perm);
109 Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
107 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size); 110 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
108 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg); 111 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
109 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages, 112 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index 13d34125c..0e2e11743 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/scope_exit.h"
4#include "core/hle/kernel/k_process.h" 5#include "core/hle/kernel/k_process.h"
5#include "core/hle/kernel/k_resource_limit.h" 6#include "core/hle/kernel/k_resource_limit.h"
6#include "core/hle/kernel/k_transfer_memory.h" 7#include "core/hle/kernel/k_transfer_memory.h"
@@ -9,28 +10,50 @@
9namespace Kernel { 10namespace Kernel {
10 11
11KTransferMemory::KTransferMemory(KernelCore& kernel) 12KTransferMemory::KTransferMemory(KernelCore& kernel)
12 : KAutoObjectWithSlabHeapAndContainer{kernel} {} 13 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
13 14
14KTransferMemory::~KTransferMemory() = default; 15KTransferMemory::~KTransferMemory() = default;
15 16
16Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size, 17Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
17 Svc::MemoryPermission owner_perm) { 18 Svc::MemoryPermission own_perm) {
18 // Set members. 19 // Set members.
19 m_owner = GetCurrentProcessPointer(m_kernel); 20 m_owner = GetCurrentProcessPointer(m_kernel);
20 21
21 // TODO(bunnei): Lock for transfer memory 22 // Get the owner page table.
23 auto& page_table = m_owner->GetPageTable();
24
25 // Construct the page group, guarding to make sure our state is valid on exit.
26 m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
27 auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
28
29 // Lock the memory.
30 R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
31 ConvertToKMemoryPermission(own_perm)));
22 32
23 // Set remaining tracking members. 33 // Set remaining tracking members.
24 m_owner->Open(); 34 m_owner->Open();
25 m_owner_perm = owner_perm; 35 m_owner_perm = own_perm;
26 m_address = address; 36 m_address = addr;
27 m_size = size;
28 m_is_initialized = true; 37 m_is_initialized = true;
38 m_is_mapped = false;
29 39
40 // We succeeded.
41 pg_guard.Cancel();
30 R_SUCCEED(); 42 R_SUCCEED();
31} 43}
32 44
33void KTransferMemory::Finalize() {} 45void KTransferMemory::Finalize() {
46 // Unlock.
47 if (!m_is_mapped) {
48 const size_t size = m_page_group->GetNumPages() * PageSize;
49 ASSERT(R_SUCCEEDED(
50 m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
51 }
52
53 // Close the page group.
54 m_page_group->Close();
55 m_page_group->Finalize();
56}
34 57
35void KTransferMemory::PostDestroy(uintptr_t arg) { 58void KTransferMemory::PostDestroy(uintptr_t arg) {
36 KProcess* owner = reinterpret_cast<KProcess*>(arg); 59 KProcess* owner = reinterpret_cast<KProcess*>(arg);
@@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
38 owner->Close(); 61 owner->Close();
39} 62}
40 63
64Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
65 // Validate the size.
66 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
67
68 // Validate the permission.
69 R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
70
71 // Lock ourselves.
72 KScopedLightLock lk(m_lock);
73
74 // Ensure we're not already mapped.
75 R_UNLESS(!m_is_mapped, ResultInvalidState);
76
77 // Map the memory.
78 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
79 ? KMemoryState::Transfered
80 : KMemoryState::SharedTransfered;
81 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
82 address, *m_page_group, state, KMemoryPermission::UserReadWrite));
83
84 // Mark ourselves as mapped.
85 m_is_mapped = true;
86
87 R_SUCCEED();
88}
89
90Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
91 // Validate the size.
92 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
93
94 // Lock ourselves.
95 KScopedLightLock lk(m_lock);
96
97 // Unmap the memory.
98 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
99 ? KMemoryState::Transfered
100 : KMemoryState::SharedTransfered;
101 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
102
103 // Mark ourselves as unmapped.
104 ASSERT(m_is_mapped);
105 m_is_mapped = false;
106
107 R_SUCCEED();
108}
109
110size_t KTransferMemory::GetSize() const {
111 return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
112}
113
41} // namespace Kernel 114} // namespace Kernel
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h
index 54f97ccb4..8a0b08761 100644
--- a/src/core/hle/kernel/k_transfer_memory.h
+++ b/src/core/hle/kernel/k_transfer_memory.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <optional>
7
8#include "core/hle/kernel/k_page_group.h"
6#include "core/hle/kernel/slab_helpers.h" 9#include "core/hle/kernel/slab_helpers.h"
7#include "core/hle/kernel/svc_types.h" 10#include "core/hle/kernel/svc_types.h"
8#include "core/hle/result.h" 11#include "core/hle/result.h"
@@ -48,16 +51,19 @@ public:
48 return m_address; 51 return m_address;
49 } 52 }
50 53
51 size_t GetSize() const { 54 size_t GetSize() const;
52 return m_is_initialized ? m_size : 0; 55
53 } 56 Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
57 Result Unmap(KProcessAddress address, size_t size);
54 58
55private: 59private:
60 std::optional<KPageGroup> m_page_group{};
56 KProcess* m_owner{}; 61 KProcess* m_owner{};
57 KProcessAddress m_address{}; 62 KProcessAddress m_address{};
63 KLightLock m_lock;
58 Svc::MemoryPermission m_owner_perm{}; 64 Svc::MemoryPermission m_owner_perm{};
59 size_t m_size{};
60 bool m_is_initialized{}; 65 bool m_is_initialized{};
66 bool m_is_mapped{};
61}; 67};
62 68
63} // namespace Kernel 69} // namespace Kernel
diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
index 7d94e7f09..1f97121b3 100644
--- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
@@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
71} 71}
72 72
73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size, 73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
74 MemoryPermission owner_perm) { 74 MemoryPermission map_perm) {
75 UNIMPLEMENTED(); 75 // Validate the address/size.
76 R_THROW(ResultNotImplemented); 76 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
77 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
78 R_UNLESS(size > 0, ResultInvalidSize);
79 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
80
81 // Validate the permission.
82 R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
83
84 // Get the transfer memory.
85 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
86 .GetHandleTable()
87 .GetObject<KTransferMemory>(trmem_handle);
88 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
89
90 // Verify that the mapping is in range.
91 R_UNLESS(GetCurrentProcess(system.Kernel())
92 .GetPageTable()
93 .CanContain(address, size, KMemoryState::Transfered),
94 ResultInvalidMemoryRegion);
95
96 // Map the transfer memory.
97 R_TRY(trmem->Map(address, size, map_perm));
98
99 // We succeeded.
100 R_SUCCEED();
77} 101}
78 102
79Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, 103Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
80 uint64_t size) { 104 uint64_t size) {
81 UNIMPLEMENTED(); 105 // Validate the address/size.
82 R_THROW(ResultNotImplemented); 106 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
107 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
108 R_UNLESS(size > 0, ResultInvalidSize);
109 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
110
111 // Get the transfer memory.
112 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
113 .GetHandleTable()
114 .GetObject<KTransferMemory>(trmem_handle);
115 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
116
117 // Verify that the mapping is in range.
118 R_UNLESS(GetCurrentProcess(system.Kernel())
119 .GetPageTable()
120 .CanContain(address, size, KMemoryState::Transfered),
121 ResultInvalidMemoryRegion);
122
123 // Unmap the transfer memory.
124 R_TRY(trmem->Unmap(address, size));
125
126 R_SUCCEED();
83} 127}
84 128
85Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address, 129Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8ffdd19e7..ac376b55a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -8,6 +8,7 @@
8#include "common/settings.h" 8#include "common/settings.h"
9#include "common/settings_enums.h" 9#include "common/settings_enums.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/core_timing.h"
11#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h" 14#include "core/file_sys/registered_cache.h"
@@ -19,6 +20,8 @@
19#include "core/hle/service/am/am.h" 20#include "core/hle/service/am/am.h"
20#include "core/hle/service/am/applet_ae.h" 21#include "core/hle/service/am/applet_ae.h"
21#include "core/hle/service/am/applet_oe.h" 22#include "core/hle/service/am/applet_oe.h"
23#include "core/hle/service/am/applets/applet_cabinet.h"
24#include "core/hle/service/am/applets/applet_mii_edit_types.h"
22#include "core/hle/service/am/applets/applet_profile_select.h" 25#include "core/hle/service/am/applets/applet_profile_select.h"
23#include "core/hle/service/am/applets/applet_web_browser.h" 26#include "core/hle/service/am/applets/applet_web_browser.h"
24#include "core/hle/service/am/applets/applets.h" 27#include "core/hle/service/am/applets/applets.h"
@@ -28,15 +31,17 @@
28#include "core/hle/service/apm/apm_controller.h" 31#include "core/hle/service/apm/apm_controller.h"
29#include "core/hle/service/apm/apm_interface.h" 32#include "core/hle/service/apm/apm_interface.h"
30#include "core/hle/service/bcat/backend/backend.h" 33#include "core/hle/service/bcat/backend/backend.h"
31#include "core/hle/service/caps/caps.h" 34#include "core/hle/service/caps/caps_types.h"
32#include "core/hle/service/filesystem/filesystem.h" 35#include "core/hle/service/filesystem/filesystem.h"
33#include "core/hle/service/ipc_helpers.h" 36#include "core/hle/service/ipc_helpers.h"
34#include "core/hle/service/ns/ns.h" 37#include "core/hle/service/ns/ns.h"
38#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
35#include "core/hle/service/nvnflinger/nvnflinger.h" 39#include "core/hle/service/nvnflinger/nvnflinger.h"
36#include "core/hle/service/pm/pm.h" 40#include "core/hle/service/pm/pm.h"
37#include "core/hle/service/server_manager.h" 41#include "core/hle/service/server_manager.h"
38#include "core/hle/service/sm/sm.h" 42#include "core/hle/service/sm/sm.h"
39#include "core/hle/service/vi/vi.h" 43#include "core/hle/service/vi/vi.h"
44#include "core/hle/service/vi/vi_results.h"
40#include "core/memory.h" 45#include "core/memory.h"
41 46
42namespace Service::AM { 47namespace Service::AM {
@@ -189,8 +194,8 @@ IDisplayController::IDisplayController(Core::System& system_)
189 {4, nullptr, "UpdateCallerAppletCaptureImage"}, 194 {4, nullptr, "UpdateCallerAppletCaptureImage"},
190 {5, nullptr, "GetLastForegroundCaptureImageEx"}, 195 {5, nullptr, "GetLastForegroundCaptureImageEx"},
191 {6, nullptr, "GetLastApplicationCaptureImageEx"}, 196 {6, nullptr, "GetLastApplicationCaptureImageEx"},
192 {7, nullptr, "GetCallerAppletCaptureImageEx"}, 197 {7, &IDisplayController::GetCallerAppletCaptureImageEx, "GetCallerAppletCaptureImageEx"},
193 {8, nullptr, "TakeScreenShotOfOwnLayer"}, 198 {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
194 {9, nullptr, "CopyBetweenCaptureBuffers"}, 199 {9, nullptr, "CopyBetweenCaptureBuffers"},
195 {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, 200 {10, nullptr, "AcquireLastApplicationCaptureBuffer"},
196 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, 201 {11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
@@ -207,8 +212,8 @@ IDisplayController::IDisplayController(Core::System& system_)
207 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, 212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
208 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, 213 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
209 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, 214 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
210 {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"}, 215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
211 {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"}, 216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
212 {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, 217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
213 }; 218 };
214 // clang-format on 219 // clang-format on
@@ -218,6 +223,38 @@ IDisplayController::IDisplayController(Core::System& system_)
218 223
219IDisplayController::~IDisplayController() = default; 224IDisplayController::~IDisplayController() = default;
220 225
226void IDisplayController::GetCallerAppletCaptureImageEx(HLERequestContext& ctx) {
227 LOG_WARNING(Service_AM, "(STUBBED) called");
228
229 IPC::ResponseBuilder rb{ctx, 4};
230 rb.Push(ResultSuccess);
231 rb.Push(1u);
232 rb.Push(0);
233}
234
235void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
236 LOG_WARNING(Service_AM, "(STUBBED) called");
237
238 IPC::ResponseBuilder rb{ctx, 2};
239 rb.Push(ResultSuccess);
240}
241
242void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called");
244
245 IPC::ResponseBuilder rb{ctx, 4};
246 rb.Push(ResultSuccess);
247 rb.Push(1U);
248 rb.Push(0);
249}
250
251void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
252 LOG_WARNING(Service_AM, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
221IDebugFunctions::IDebugFunctions(Core::System& system_) 258IDebugFunctions::IDebugFunctions(Core::System& system_)
222 : ServiceFramework{system_, "IDebugFunctions"} { 259 : ServiceFramework{system_, "IDebugFunctions"} {
223 // clang-format off 260 // clang-format off
@@ -277,14 +314,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger&
277 {20, nullptr, "SetDesirableKeyboardLayout"}, 314 {20, nullptr, "SetDesirableKeyboardLayout"},
278 {21, nullptr, "GetScreenShotProgramId"}, 315 {21, nullptr, "GetScreenShotProgramId"},
279 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, 316 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
280 {41, nullptr, "IsSystemBufferSharingEnabled"}, 317 {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"},
281 {42, nullptr, "GetSystemSharedLayerHandle"}, 318 {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"},
282 {43, nullptr, "GetSystemSharedBufferHandle"}, 319 {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"},
283 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, 320 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
284 {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, 321 {45, nullptr, "SetManagedDisplayLayerSeparationMode"},
285 {46, nullptr, "SetRecordingLayerCompositionEnabled"}, 322 {46, nullptr, "SetRecordingLayerCompositionEnabled"},
286 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, 323 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
287 {51, nullptr, "ApproveToDisplay"}, 324 {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
288 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, 325 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
289 {61, nullptr, "SetMediaPlaybackState"}, 326 {61, nullptr, "SetMediaPlaybackState"},
290 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, 327 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
@@ -483,6 +520,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) {
483 rb.Push(*layer_id); 520 rb.Push(*layer_id);
484} 521}
485 522
523void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) {
524 LOG_WARNING(Service_AM, "(STUBBED) called");
525
526 IPC::ResponseBuilder rb{ctx, 2};
527 rb.Push(this->EnsureBufferSharingEnabled());
528}
529
530void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) {
531 LOG_WARNING(Service_AM, "(STUBBED) called");
532
533 IPC::ResponseBuilder rb{ctx, 6};
534 rb.Push(this->EnsureBufferSharingEnabled());
535 rb.Push<s64>(system_shared_buffer_id);
536 rb.Push<s64>(system_shared_layer_id);
537}
538
539void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
540 LOG_WARNING(Service_AM, "(STUBBED) called");
541
542 IPC::ResponseBuilder rb{ctx, 4};
543 rb.Push(this->EnsureBufferSharingEnabled());
544 rb.Push<s64>(system_shared_buffer_id);
545}
546
547Result ISelfController::EnsureBufferSharingEnabled() {
548 if (buffer_sharing_enabled) {
549 return ResultSuccess;
550 }
551
552 if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) {
553 return VI::ResultOperationFailed;
554 }
555
556 const auto display_id = nvnflinger.OpenDisplay("Default");
557 const auto result = nvnflinger.GetSystemBufferManager().Initialize(
558 &system_shared_buffer_id, &system_shared_layer_id, *display_id);
559
560 if (result.IsSuccess()) {
561 buffer_sharing_enabled = true;
562 }
563
564 return result;
565}
566
486void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) { 567void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) {
487 LOG_WARNING(Service_AM, "(STUBBED) called"); 568 LOG_WARNING(Service_AM, "(STUBBED) called");
488 569
@@ -508,6 +589,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) {
508 rb.Push(ResultSuccess); 589 rb.Push(ResultSuccess);
509} 590}
510 591
592void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
593 LOG_WARNING(Service_AM, "(STUBBED) called");
594
595 IPC::ResponseBuilder rb{ctx, 2};
596 rb.Push(ResultSuccess);
597}
598
511void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { 599void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
512 IPC::RequestParser rp{ctx}; 600 IPC::RequestParser rp{ctx};
513 idle_time_detection_extension = rp.Pop<u32>(); 601 idle_time_detection_extension = rp.Pop<u32>();
@@ -676,9 +764,70 @@ void AppletMessageQueue::OperationModeChanged() {
676 on_operation_mode_changed->Signal(); 764 on_operation_mode_changed->Signal();
677} 765}
678 766
767ILockAccessor::ILockAccessor(Core::System& system_)
768 : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
769 // clang-format off
770 static const FunctionInfo functions[] = {
771 {1, &ILockAccessor::TryLock, "TryLock"},
772 {2, &ILockAccessor::Unlock, "Unlock"},
773 {3, &ILockAccessor::GetEvent, "GetEvent"},
774 {4,&ILockAccessor::IsLocked, "IsLocked"},
775 };
776 // clang-format on
777
778 RegisterHandlers(functions);
779
780 lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
781}
782
783ILockAccessor::~ILockAccessor() = default;
784
785void ILockAccessor::TryLock(HLERequestContext& ctx) {
786 IPC::RequestParser rp{ctx};
787 const auto return_handle = rp.Pop<bool>();
788
789 LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
790
791 // TODO: When return_handle is true this function should return the lock handle
792
793 is_locked = true;
794
795 IPC::ResponseBuilder rb{ctx, 3};
796 rb.Push(ResultSuccess);
797 rb.Push<u8>(is_locked);
798}
799
800void ILockAccessor::Unlock(HLERequestContext& ctx) {
801 LOG_INFO(Service_AM, "called");
802
803 is_locked = false;
804
805 IPC::ResponseBuilder rb{ctx, 2};
806 rb.Push(ResultSuccess);
807}
808
809void ILockAccessor::GetEvent(HLERequestContext& ctx) {
810 LOG_INFO(Service_AM, "called");
811
812 lock_event->Signal();
813
814 IPC::ResponseBuilder rb{ctx, 2, 1};
815 rb.Push(ResultSuccess);
816 rb.PushCopyObjects(lock_event->GetReadableEvent());
817}
818
819void ILockAccessor::IsLocked(HLERequestContext& ctx) {
820 LOG_INFO(Service_AM, "called");
821
822 IPC::ResponseBuilder rb{ctx, 2};
823 rb.Push(ResultSuccess);
824 rb.Push<u8>(is_locked);
825}
826
679ICommonStateGetter::ICommonStateGetter(Core::System& system_, 827ICommonStateGetter::ICommonStateGetter(Core::System& system_,
680 std::shared_ptr<AppletMessageQueue> msg_queue_) 828 std::shared_ptr<AppletMessageQueue> msg_queue_)
681 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { 829 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
830 service_context{system_, "ICommonStateGetter"} {
682 // clang-format off 831 // clang-format off
683 static const FunctionInfo functions[] = { 832 static const FunctionInfo functions[] = {
684 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, 833 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -691,14 +840,14 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
691 {7, nullptr, "GetCradleStatus"}, 840 {7, nullptr, "GetCradleStatus"},
692 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"}, 841 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
693 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, 842 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
694 {10, nullptr, "RequestToAcquireSleepLock"}, 843 {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"},
695 {11, nullptr, "ReleaseSleepLock"}, 844 {11, nullptr, "ReleaseSleepLock"},
696 {12, nullptr, "ReleaseSleepLockTransiently"}, 845 {12, nullptr, "ReleaseSleepLockTransiently"},
697 {13, nullptr, "GetAcquiredSleepLockEvent"}, 846 {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"},
698 {14, nullptr, "GetWakeupCount"}, 847 {14, nullptr, "GetWakeupCount"},
699 {20, nullptr, "PushToGeneralChannel"}, 848 {20, nullptr, "PushToGeneralChannel"},
700 {30, nullptr, "GetHomeButtonReaderLockAccessor"}, 849 {30, nullptr, "GetHomeButtonReaderLockAccessor"},
701 {31, nullptr, "GetReaderLockAccessorEx"}, 850 {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
702 {32, nullptr, "GetWriterLockAccessorEx"}, 851 {32, nullptr, "GetWriterLockAccessorEx"},
703 {40, nullptr, "GetCradleFwVersion"}, 852 {40, nullptr, "GetCradleFwVersion"},
704 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, 853 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -716,7 +865,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
716 {65, nullptr, "GetApplicationIdByContentActionName"}, 865 {65, nullptr, "GetApplicationIdByContentActionName"},
717 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, 866 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
718 {67, nullptr, "CancelCpuBoostMode"}, 867 {67, nullptr, "CancelCpuBoostMode"},
719 {68, nullptr, "GetBuiltInDisplayType"}, 868 {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
720 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"}, 869 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
721 {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, 870 {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
722 {91, nullptr, "GetCurrentPerformanceConfiguration"}, 871 {91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -724,7 +873,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
724 {110, nullptr, "OpenMyGpuErrorHandler"}, 873 {110, nullptr, "OpenMyGpuErrorHandler"},
725 {120, nullptr, "GetAppletLaunchedHistory"}, 874 {120, nullptr, "GetAppletLaunchedHistory"},
726 {200, nullptr, "GetOperationModeSystemInfo"}, 875 {200, nullptr, "GetOperationModeSystemInfo"},
727 {300, nullptr, "GetSettingsPlatformRegion"}, 876 {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
728 {400, nullptr, "ActivateMigrationService"}, 877 {400, nullptr, "ActivateMigrationService"},
729 {401, nullptr, "DeactivateMigrationService"}, 878 {401, nullptr, "DeactivateMigrationService"},
730 {500, nullptr, "DisableSleepTillShutdown"}, 879 {500, nullptr, "DisableSleepTillShutdown"},
@@ -736,6 +885,12 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
736 // clang-format on 885 // clang-format on
737 886
738 RegisterHandlers(functions); 887 RegisterHandlers(functions);
888
889 sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent");
890
891 // Configure applets to be in foreground state
892 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
893 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
739} 894}
740 895
741ICommonStateGetter::~ICommonStateGetter() = default; 896ICommonStateGetter::~ICommonStateGetter() = default;
@@ -781,6 +936,36 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) {
781 rb.Push(static_cast<u8>(FocusState::InFocus)); 936 rb.Push(static_cast<u8>(FocusState::InFocus));
782} 937}
783 938
939void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
940 LOG_WARNING(Service_AM, "(STUBBED) called");
941
942 // Sleep lock is acquired immediately.
943 sleep_lock_event->Signal();
944
945 IPC::ResponseBuilder rb{ctx, 2};
946 rb.Push(ResultSuccess);
947}
948
949void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
950 IPC::RequestParser rp{ctx};
951 const auto unknown = rp.Pop<u32>();
952
953 LOG_INFO(Service_AM, "called, unknown={}", unknown);
954
955 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
956
957 rb.Push(ResultSuccess);
958 rb.PushIpcInterface<ILockAccessor>(system);
959}
960
961void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
962 LOG_WARNING(Service_AM, "called");
963
964 IPC::ResponseBuilder rb{ctx, 2, 1};
965 rb.Push(ResultSuccess);
966 rb.PushCopyObjects(sleep_lock_event->GetReadableEvent());
967}
968
784void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) { 969void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) {
785 LOG_DEBUG(Service_AM, "called"); 970 LOG_DEBUG(Service_AM, "called");
786 971
@@ -857,6 +1042,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
857 apm_sys->SetCpuBoostMode(ctx); 1042 apm_sys->SetCpuBoostMode(ctx);
858} 1043}
859 1044
1045void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
1046 LOG_WARNING(Service_AM, "(STUBBED) called");
1047
1048 IPC::ResponseBuilder rb{ctx, 3};
1049 rb.Push(ResultSuccess);
1050 rb.Push(0);
1051}
1052
860void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) { 1053void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
861 IPC::RequestParser rp{ctx}; 1054 IPC::RequestParser rp{ctx};
862 const auto system_button{rp.PopEnum<SystemButtonType>()}; 1055 const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -867,6 +1060,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
867 rb.Push(ResultSuccess); 1060 rb.Push(ResultSuccess);
868} 1061}
869 1062
1063void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
1064 LOG_WARNING(Service_AM, "(STUBBED) called");
1065
1066 IPC::ResponseBuilder rb{ctx, 3};
1067 rb.Push(ResultSuccess);
1068 rb.PushEnum(SysPlatformRegion::Global);
1069}
1070
870void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( 1071void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
871 HLERequestContext& ctx) { 1072 HLERequestContext& ctx) {
872 LOG_WARNING(Service_AM, "(STUBBED) called"); 1073 LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1324,18 +1525,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
1324 1525
1325ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) 1526ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1326 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { 1527 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
1528 // clang-format off
1327 static const FunctionInfo functions[] = { 1529 static const FunctionInfo functions[] = {
1328 {0, nullptr, "PopInData"}, 1530 {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
1329 {1, nullptr, "PushOutData"}, 1531 {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
1330 {2, nullptr, "PopInteractiveInData"}, 1532 {2, nullptr, "PopInteractiveInData"},
1331 {3, nullptr, "PushInteractiveOutData"}, 1533 {3, nullptr, "PushInteractiveOutData"},
1332 {5, nullptr, "GetPopInDataEvent"}, 1534 {5, nullptr, "GetPopInDataEvent"},
1333 {6, nullptr, "GetPopInteractiveInDataEvent"}, 1535 {6, nullptr, "GetPopInteractiveInDataEvent"},
1334 {10, nullptr, "ExitProcessAndReturn"}, 1536 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
1335 {11, nullptr, "GetLibraryAppletInfo"}, 1537 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
1336 {12, nullptr, "GetMainAppletIdentityInfo"}, 1538 {12, nullptr, "GetMainAppletIdentityInfo"},
1337 {13, nullptr, "CanUseApplicationCore"}, 1539 {13, nullptr, "CanUseApplicationCore"},
1338 {14, nullptr, "GetCallerAppletIdentityInfo"}, 1540 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
1339 {15, nullptr, "GetMainAppletApplicationControlProperty"}, 1541 {15, nullptr, "GetMainAppletApplicationControlProperty"},
1340 {16, nullptr, "GetMainAppletStorageId"}, 1542 {16, nullptr, "GetMainAppletStorageId"},
1341 {17, nullptr, "GetCallerAppletIdentityInfoStack"}, 1543 {17, nullptr, "GetCallerAppletIdentityInfoStack"},
@@ -1361,10 +1563,200 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1361 {140, nullptr, "SetApplicationMemoryReservation"}, 1563 {140, nullptr, "SetApplicationMemoryReservation"},
1362 {150, nullptr, "ShouldSetGpuTimeSliceManually"}, 1564 {150, nullptr, "ShouldSetGpuTimeSliceManually"},
1363 }; 1565 };
1566 // clang-format on
1364 RegisterHandlers(functions); 1567 RegisterHandlers(functions);
1568
1569 switch (system.GetAppletManager().GetCurrentAppletId()) {
1570 case Applets::AppletId::Cabinet:
1571 PushInShowCabinetData();
1572 break;
1573 case Applets::AppletId::MiiEdit:
1574 PushInShowMiiEditData();
1575 break;
1576 case Applets::AppletId::PhotoViewer:
1577 PushInShowAlbum();
1578 break;
1579 default:
1580 break;
1581 }
1365} 1582}
1366 1583
1367ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; 1584ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
1585void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
1586 LOG_INFO(Service_AM, "called");
1587
1588 if (queue_data.empty()) {
1589 IPC::ResponseBuilder rb{ctx, 2};
1590 rb.Push(ResultNoDataInChannel);
1591 return;
1592 }
1593
1594 auto data = queue_data.front();
1595 queue_data.pop_front();
1596
1597 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1598 rb.Push(ResultSuccess);
1599 rb.PushIpcInterface<IStorage>(system, std::move(data));
1600}
1601
1602void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
1603 LOG_WARNING(Service_AM, "(STUBBED) called");
1604
1605 IPC::ResponseBuilder rb{ctx, 2};
1606 rb.Push(ResultSuccess);
1607}
1608
1609void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
1610 LOG_WARNING(Service_AM, "(STUBBED) called");
1611
1612 system.Exit();
1613
1614 IPC::ResponseBuilder rb{ctx, 2};
1615 rb.Push(ResultSuccess);
1616}
1617
1618void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1619 struct LibraryAppletInfo {
1620 Applets::AppletId applet_id;
1621 Applets::LibraryAppletMode library_applet_mode;
1622 };
1623
1624 LOG_WARNING(Service_AM, "(STUBBED) called");
1625
1626 const LibraryAppletInfo applet_info{
1627 .applet_id = system.GetAppletManager().GetCurrentAppletId(),
1628 .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
1629 };
1630
1631 IPC::ResponseBuilder rb{ctx, 4};
1632 rb.Push(ResultSuccess);
1633 rb.PushRaw(applet_info);
1634}
1635
1636void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
1637 struct AppletIdentityInfo {
1638 Applets::AppletId applet_id;
1639 INSERT_PADDING_BYTES(0x4);
1640 u64 application_id;
1641 };
1642
1643 LOG_WARNING(Service_AM, "(STUBBED) called");
1644
1645 const AppletIdentityInfo applet_info{
1646 .applet_id = Applets::AppletId::QLaunch,
1647 .application_id = 0x0100000000001000ull,
1648 };
1649
1650 IPC::ResponseBuilder rb{ctx, 6};
1651 rb.Push(ResultSuccess);
1652 rb.PushRaw(applet_info);
1653}
1654
1655void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1656 const Applets::CommonArguments arguments{
1657 .arguments_version = Applets::CommonArgumentVersion::Version3,
1658 .size = Applets::CommonArgumentSize::Version3,
1659 .library_version = 1,
1660 .theme_color = Applets::ThemeColor::BasicBlack,
1661 .play_startup_sound = true,
1662 .system_tick = system.CoreTiming().GetClockTicks(),
1663 };
1664
1665 std::vector<u8> argument_data(sizeof(arguments));
1666 std::vector<u8> settings_data{2};
1667 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1668 queue_data.emplace_back(std::move(argument_data));
1669 queue_data.emplace_back(std::move(settings_data));
1670}
1671
1672void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1673 const Applets::CommonArguments arguments{
1674 .arguments_version = Applets::CommonArgumentVersion::Version3,
1675 .size = Applets::CommonArgumentSize::Version3,
1676 .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1),
1677 .theme_color = Applets::ThemeColor::BasicBlack,
1678 .play_startup_sound = true,
1679 .system_tick = system.CoreTiming().GetClockTicks(),
1680 };
1681
1682 const Applets::StartParamForAmiiboSettings amiibo_settings{
1683 .param_1 = 0,
1684 .applet_mode = system.GetAppletManager().GetCabinetMode(),
1685 .flags = Applets::CabinetFlags::None,
1686 .amiibo_settings_1 = 0,
1687 .device_handle = 0,
1688 .tag_info{},
1689 .register_info{},
1690 .amiibo_settings_3{},
1691 };
1692
1693 std::vector<u8> argument_data(sizeof(arguments));
1694 std::vector<u8> settings_data(sizeof(amiibo_settings));
1695 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1696 std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings));
1697 queue_data.emplace_back(std::move(argument_data));
1698 queue_data.emplace_back(std::move(settings_data));
1699}
1700
1701void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
1702 struct MiiEditV3 {
1703 Applets::MiiEditAppletInputCommon common;
1704 Applets::MiiEditAppletInputV3 input;
1705 };
1706 static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
1707
1708 MiiEditV3 mii_arguments{
1709 .common =
1710 {
1711 .version = Applets::MiiEditAppletVersion::Version3,
1712 .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
1713 },
1714 .input{},
1715 };
1716
1717 std::vector<u8> argument_data(sizeof(mii_arguments));
1718 std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
1719
1720 queue_data.emplace_back(std::move(argument_data));
1721}
1722
1723IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
1724 : ServiceFramework{system_, "IAppletCommonFunctions"} {
1725 // clang-format off
1726 static const FunctionInfo functions[] = {
1727 {0, nullptr, "SetTerminateResult"},
1728 {10, nullptr, "ReadThemeStorage"},
1729 {11, nullptr, "WriteThemeStorage"},
1730 {20, nullptr, "PushToAppletBoundChannel"},
1731 {21, nullptr, "TryPopFromAppletBoundChannel"},
1732 {40, nullptr, "GetDisplayLogicalResolution"},
1733 {42, nullptr, "SetDisplayMagnification"},
1734 {50, nullptr, "SetHomeButtonDoubleClickEnabled"},
1735 {51, nullptr, "GetHomeButtonDoubleClickEnabled"},
1736 {52, nullptr, "IsHomeButtonShortPressedBlocked"},
1737 {60, nullptr, "IsVrModeCurtainRequired"},
1738 {61, nullptr, "IsSleepRequiredByHighTemperature"},
1739 {62, nullptr, "IsSleepRequiredByLowBattery"},
1740 {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
1741 {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
1742 {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
1743 {90, nullptr, "OpenNamedChannelAsParent"},
1744 {91, nullptr, "OpenNamedChannelAsChild"},
1745 {100, nullptr, "SetApplicationCoreUsageMode"},
1746 };
1747 // clang-format on
1748
1749 RegisterHandlers(functions);
1750}
1751
1752IAppletCommonFunctions::~IAppletCommonFunctions() = default;
1753
1754void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
1755 LOG_WARNING(Service_AM, "(STUBBED) called");
1756
1757 IPC::ResponseBuilder rb{ctx, 2};
1758 rb.Push(ResultSuccess);
1759}
1368 1760
1369IApplicationFunctions::IApplicationFunctions(Core::System& system_) 1761IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1370 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, 1762 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@@ -1941,9 +2333,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
1941 2333
1942void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { 2334void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
1943 auto message_queue = std::make_shared<AppletMessageQueue>(system); 2335 auto message_queue = std::make_shared<AppletMessageQueue>(system);
1944 // Needed on game boot
1945 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
1946
1947 auto server_manager = std::make_unique<ServerManager>(system); 2336 auto server_manager = std::make_unique<ServerManager>(system);
1948 2337
1949 server_manager->RegisterNamedService( 2338 server_manager->RegisterNamedService(
@@ -2049,8 +2438,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2049 : ServiceFramework{system_, "IProcessWindingController"} { 2438 : ServiceFramework{system_, "IProcessWindingController"} {
2050 // clang-format off 2439 // clang-format off
2051 static const FunctionInfo functions[] = { 2440 static const FunctionInfo functions[] = {
2052 {0, nullptr, "GetLaunchReason"}, 2441 {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
2053 {11, nullptr, "OpenCallingLibraryApplet"}, 2442 {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
2054 {21, nullptr, "PushContext"}, 2443 {21, nullptr, "PushContext"},
2055 {22, nullptr, "PopContext"}, 2444 {22, nullptr, "PopContext"},
2056 {23, nullptr, "CancelWindingReservation"}, 2445 {23, nullptr, "CancelWindingReservation"},
@@ -2064,4 +2453,47 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2064} 2453}
2065 2454
2066IProcessWindingController::~IProcessWindingController() = default; 2455IProcessWindingController::~IProcessWindingController() = default;
2456
2457void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
2458 LOG_WARNING(Service_AM, "(STUBBED) called");
2459
2460 struct AppletProcessLaunchReason {
2461 u8 flag;
2462 INSERT_PADDING_BYTES(3);
2463 };
2464 static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
2465 "AppletProcessLaunchReason is an invalid size");
2466
2467 AppletProcessLaunchReason reason{
2468 .flag = 0,
2469 };
2470
2471 IPC::ResponseBuilder rb{ctx, 3};
2472 rb.Push(ResultSuccess);
2473 rb.PushRaw(reason);
2474}
2475
2476void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
2477 const auto applet_id = system.GetAppletManager().GetCurrentAppletId();
2478 const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
2479
2480 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
2481 applet_mode);
2482
2483 const auto& applet_manager{system.GetAppletManager()};
2484 const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
2485
2486 if (applet == nullptr) {
2487 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
2488
2489 IPC::ResponseBuilder rb{ctx, 2};
2490 rb.Push(ResultUnknown);
2491 return;
2492 }
2493
2494 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
2495 rb.Push(ResultSuccess);
2496 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
2497}
2498
2067} // namespace Service::AM 2499} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f86841c60..4a045cfd4 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -120,6 +120,12 @@ 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 GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
127 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
123}; 129};
124 130
125class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { 131class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -147,9 +153,13 @@ private:
147 void SetRestartMessageEnabled(HLERequestContext& ctx); 153 void SetRestartMessageEnabled(HLERequestContext& ctx);
148 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx); 154 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx);
149 void SetAlbumImageOrientation(HLERequestContext& ctx); 155 void SetAlbumImageOrientation(HLERequestContext& ctx);
156 void IsSystemBufferSharingEnabled(HLERequestContext& ctx);
157 void GetSystemSharedBufferHandle(HLERequestContext& ctx);
158 void GetSystemSharedLayerHandle(HLERequestContext& ctx);
150 void CreateManagedDisplayLayer(HLERequestContext& ctx); 159 void CreateManagedDisplayLayer(HLERequestContext& ctx);
151 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); 160 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
152 void SetHandlesRequestToDisplay(HLERequestContext& ctx); 161 void SetHandlesRequestToDisplay(HLERequestContext& ctx);
162 void ApproveToDisplay(HLERequestContext& ctx);
153 void SetIdleTimeDetectionExtension(HLERequestContext& ctx); 163 void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
154 void GetIdleTimeDetectionExtension(HLERequestContext& ctx); 164 void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
155 void ReportUserIsActive(HLERequestContext& ctx); 165 void ReportUserIsActive(HLERequestContext& ctx);
@@ -161,6 +171,8 @@ private:
161 void SaveCurrentScreenshot(HLERequestContext& ctx); 171 void SaveCurrentScreenshot(HLERequestContext& ctx);
162 void SetRecordVolumeMuted(HLERequestContext& ctx); 172 void SetRecordVolumeMuted(HLERequestContext& ctx);
163 173
174 Result EnsureBufferSharingEnabled();
175
164 enum class ScreenshotPermission : u32 { 176 enum class ScreenshotPermission : u32 {
165 Inherit = 0, 177 Inherit = 0,
166 Enable = 1, 178 Enable = 1,
@@ -176,10 +188,30 @@ private:
176 188
177 u32 idle_time_detection_extension = 0; 189 u32 idle_time_detection_extension = 0;
178 u64 num_fatal_sections_entered = 0; 190 u64 num_fatal_sections_entered = 0;
191 u64 system_shared_buffer_id = 0;
192 u64 system_shared_layer_id = 0;
179 bool is_auto_sleep_disabled = false; 193 bool is_auto_sleep_disabled = false;
194 bool buffer_sharing_enabled = false;
180 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; 195 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
181}; 196};
182 197
198class ILockAccessor final : public ServiceFramework<ILockAccessor> {
199public:
200 explicit ILockAccessor(Core::System& system_);
201 ~ILockAccessor() override;
202
203private:
204 void TryLock(HLERequestContext& ctx);
205 void Unlock(HLERequestContext& ctx);
206 void GetEvent(HLERequestContext& ctx);
207 void IsLocked(HLERequestContext& ctx);
208
209 bool is_locked{};
210
211 Kernel::KEvent* lock_event;
212 KernelHelpers::ServiceContext service_context;
213};
214
183class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 215class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
184public: 216public:
185 explicit ICommonStateGetter(Core::System& system_, 217 explicit ICommonStateGetter(Core::System& system_,
@@ -212,9 +244,17 @@ private:
212 CaptureButtonLongPressing, 244 CaptureButtonLongPressing,
213 }; 245 };
214 246
247 enum class SysPlatformRegion : s32 {
248 Global = 1,
249 Terra = 2,
250 };
251
215 void GetEventHandle(HLERequestContext& ctx); 252 void GetEventHandle(HLERequestContext& ctx);
216 void ReceiveMessage(HLERequestContext& ctx); 253 void ReceiveMessage(HLERequestContext& ctx);
217 void GetCurrentFocusState(HLERequestContext& ctx); 254 void GetCurrentFocusState(HLERequestContext& ctx);
255 void RequestToAcquireSleepLock(HLERequestContext& ctx);
256 void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
257 void GetReaderLockAccessorEx(HLERequestContext& ctx);
218 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx); 258 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
219 void GetOperationMode(HLERequestContext& ctx); 259 void GetOperationMode(HLERequestContext& ctx);
220 void GetPerformanceMode(HLERequestContext& ctx); 260 void GetPerformanceMode(HLERequestContext& ctx);
@@ -226,11 +266,15 @@ private:
226 void EndVrModeEx(HLERequestContext& ctx); 266 void EndVrModeEx(HLERequestContext& ctx);
227 void GetDefaultDisplayResolution(HLERequestContext& ctx); 267 void GetDefaultDisplayResolution(HLERequestContext& ctx);
228 void SetCpuBoostMode(HLERequestContext& ctx); 268 void SetCpuBoostMode(HLERequestContext& ctx);
269 void GetBuiltInDisplayType(HLERequestContext& ctx);
229 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 270 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
271 void GetSettingsPlatformRegion(HLERequestContext& ctx);
230 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 272 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
231 273
232 std::shared_ptr<AppletMessageQueue> msg_queue; 274 std::shared_ptr<AppletMessageQueue> msg_queue;
233 bool vr_mode_state{}; 275 bool vr_mode_state{};
276 Kernel::KEvent* sleep_lock_event;
277 KernelHelpers::ServiceContext service_context;
234}; 278};
235 279
236class IStorageImpl { 280class IStorageImpl {
@@ -294,6 +338,28 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
294public: 338public:
295 explicit ILibraryAppletSelfAccessor(Core::System& system_); 339 explicit ILibraryAppletSelfAccessor(Core::System& system_);
296 ~ILibraryAppletSelfAccessor() override; 340 ~ILibraryAppletSelfAccessor() override;
341
342private:
343 void PopInData(HLERequestContext& ctx);
344 void PushOutData(HLERequestContext& ctx);
345 void GetLibraryAppletInfo(HLERequestContext& ctx);
346 void ExitProcessAndReturn(HLERequestContext& ctx);
347 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
348
349 void PushInShowAlbum();
350 void PushInShowCabinetData();
351 void PushInShowMiiEditData();
352
353 std::deque<std::vector<u8>> queue_data;
354};
355
356class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
357public:
358 explicit IAppletCommonFunctions(Core::System& system_);
359 ~IAppletCommonFunctions() override;
360
361private:
362 void SetCpuBoostRequestPriority(HLERequestContext& ctx);
297}; 363};
298 364
299class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 365class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -378,6 +444,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
378public: 444public:
379 explicit IProcessWindingController(Core::System& system_); 445 explicit IProcessWindingController(Core::System& system_);
380 ~IProcessWindingController() override; 446 ~IProcessWindingController() override;
447
448private:
449 void GetLaunchReason(HLERequestContext& ctx);
450 void OpenCallingLibraryApplet(HLERequestContext& ctx);
381}; 451};
382 452
383void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); 453void 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..e30e6478a 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -27,9 +27,9 @@ 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, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
32 {23, nullptr, "GetGlobalStateController"}, 32 {23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
34 }; 34 };
35 // clang-format on 35 // clang-format on
@@ -86,28 +86,52 @@ 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 GetHomeMenuFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called");
115
116 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
117 rb.Push(ResultSuccess);
118 rb.PushIpcInterface<IHomeMenuFunctions>(system);
119 }
120
121 void GetGlobalStateController(HLERequestContext& ctx) {
122 LOG_DEBUG(Service_AM, "called");
123
124 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
125 rb.Push(ResultSuccess);
126 rb.PushIpcInterface<IGlobalStateController>(system);
127 }
128
129 void GetDebugFunctions(HLERequestContext& ctx) {
130 LOG_DEBUG(Service_AM, "called");
131
132 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
133 rb.Push(ResultSuccess);
134 rb.PushIpcInterface<IDebugFunctions>(system);
111 } 135 }
112 136
113 Nvnflinger::Nvnflinger& nvnflinger; 137 Nvnflinger::Nvnflinger& nvnflinger;
@@ -133,7 +157,7 @@ public:
133 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"}, 157 {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
134 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"}, 158 {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
135 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"}, 159 {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
136 {23, nullptr, "GetAppletCommonFunctions"}, 160 {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
137 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 161 {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
138 }; 162 };
139 // clang-format on 163 // clang-format on
@@ -182,14 +206,6 @@ private:
182 rb.PushIpcInterface<IDisplayController>(system); 206 rb.PushIpcInterface<IDisplayController>(system);
183 } 207 }
184 208
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) { 209 void GetLibraryAppletCreator(HLERequestContext& ctx) {
194 LOG_DEBUG(Service_AM, "called"); 210 LOG_DEBUG(Service_AM, "called");
195 211
@@ -222,6 +238,22 @@ private:
222 rb.PushIpcInterface<IApplicationCreator>(system); 238 rb.PushIpcInterface<IApplicationCreator>(system);
223 } 239 }
224 240
241 void GetAppletCommonFunctions(HLERequestContext& ctx) {
242 LOG_DEBUG(Service_AM, "called");
243
244 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
245 rb.Push(ResultSuccess);
246 rb.PushIpcInterface<IAppletCommonFunctions>(system);
247 }
248
249 void GetDebugFunctions(HLERequestContext& ctx) {
250 LOG_DEBUG(Service_AM, "called");
251
252 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
253 rb.Push(ResultSuccess);
254 rb.PushIpcInterface<IDebugFunctions>(system);
255 }
256
225 Nvnflinger::Nvnflinger& nvnflinger; 257 Nvnflinger::Nvnflinger& nvnflinger;
226 std::shared_ptr<AppletMessageQueue> msg_queue; 258 std::shared_ptr<AppletMessageQueue> msg_queue;
227}; 259};
diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h
index b56427021..f498796f7 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.h
+++ b/src/core/hle/service/am/applets/applet_cabinet.h
@@ -29,6 +29,15 @@ enum class CabinetAppletVersion : u32 {
29 Version1 = 0x1, 29 Version1 = 0x1,
30}; 30};
31 31
32enum class CabinetFlags : u8 {
33 None = 0,
34 DeviceHandle = 1 << 0,
35 TagInfo = 1 << 1,
36 RegisterInfo = 1 << 2,
37 All = DeviceHandle | TagInfo | RegisterInfo,
38};
39DECLARE_ENUM_FLAG_OPERATORS(CabinetFlags)
40
32enum class CabinetResult : u8 { 41enum class CabinetResult : u8 {
33 Cancel = 0, 42 Cancel = 0,
34 TagInfo = 1 << 1, 43 TagInfo = 1 << 1,
@@ -51,7 +60,7 @@ static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
51struct StartParamForAmiiboSettings { 60struct StartParamForAmiiboSettings {
52 u8 param_1; 61 u8 param_1;
53 Service::NFP::CabinetMode applet_mode; 62 Service::NFP::CabinetMode applet_mode;
54 u8 flags; 63 CabinetFlags flags;
55 u8 amiibo_settings_1; 64 u8 amiibo_settings_1;
56 u64 device_handle; 65 u64 device_handle;
57 Service::NFP::TagInfo tag_info; 66 Service::NFP::TagInfo tag_info;
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index b46ea840c..5d17c353f 100644
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -138,6 +138,10 @@ void Error::Initialize() {
138 CopyArgumentData(data, args->application_error); 138 CopyArgumentData(data, args->application_error);
139 error_code = Result(args->application_error.error_code); 139 error_code = Result(args->application_error.error_code);
140 break; 140 break;
141 case ErrorAppletMode::ShowErrorPctl:
142 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64);
144 break;
141 case ErrorAppletMode::ShowErrorRecord: 145 case ErrorAppletMode::ShowErrorRecord:
142 CopyArgumentData(data, args->error_record); 146 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64); 147 error_code = Decode64BitError(args->error_record.error_code_64);
@@ -191,6 +195,7 @@ void Error::Execute() {
191 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback); 195 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
192 break; 196 break;
193 } 197 }
198 case ErrorAppletMode::ShowErrorPctl:
194 case ErrorAppletMode::ShowErrorRecord: 199 case ErrorAppletMode::ShowErrorRecord:
195 reporter.SaveErrorReport(title_id, error_code, 200 reporter.SaveErrorReport(title_id, error_code,
196 fmt::format("{:016X}", args->error_record.posix_time)); 201 fmt::format("{:016X}", args->error_record.posix_time));
diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp
index 8b352020e..c0032f652 100644
--- a/src/core/hle/service/am/applets/applet_general_backend.cpp
+++ b/src/core/hle/service/am/applets/applet_general_backend.cpp
@@ -223,9 +223,9 @@ void StubApplet::Initialize() {
223 223
224 const auto data = broker.PeekDataToAppletForDebug(); 224 const auto data = broker.PeekDataToAppletForDebug();
225 system.GetReporter().SaveUnimplementedAppletReport( 225 system.GetReporter().SaveUnimplementedAppletReport(
226 static_cast<u32>(id), common_args.arguments_version, common_args.library_version, 226 static_cast<u32>(id), static_cast<u32>(common_args.arguments_version),
227 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, 227 common_args.library_version, static_cast<u32>(common_args.theme_color),
228 data.normal, data.interactive); 228 common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive);
229 229
230 LogCurrentStorage(broker, "Initialize"); 230 LogCurrentStorage(broker, "Initialize");
231} 231}
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/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 10afbc2da..89d5434af 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -199,6 +199,14 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
199 return frontend; 199 return frontend;
200} 200}
201 201
202NFP::CabinetMode AppletManager::GetCabinetMode() const {
203 return cabinet_mode;
204}
205
206AppletId AppletManager::GetCurrentAppletId() const {
207 return current_applet_id;
208}
209
202void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { 210void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
203 if (set.cabinet != nullptr) { 211 if (set.cabinet != nullptr) {
204 frontend.cabinet = std::move(set.cabinet); 212 frontend.cabinet = std::move(set.cabinet);
@@ -237,6 +245,14 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
237 } 245 }
238} 246}
239 247
248void AppletManager::SetCabinetMode(NFP::CabinetMode mode) {
249 cabinet_mode = mode;
250}
251
252void AppletManager::SetCurrentAppletId(AppletId applet_id) {
253 current_applet_id = applet_id;
254}
255
240void AppletManager::SetDefaultAppletFrontendSet() { 256void AppletManager::SetDefaultAppletFrontendSet() {
241 ClearAll(); 257 ClearAll();
242 SetDefaultAppletsIfMissing(); 258 SetDefaultAppletsIfMissing();
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 12f374199..f02bbc450 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -34,6 +34,10 @@ class KEvent;
34class KReadableEvent; 34class KReadableEvent;
35} // namespace Kernel 35} // namespace Kernel
36 36
37namespace Service::NFP {
38enum class CabinetMode : u8;
39} // namespace Service::NFP
40
37namespace Service::AM { 41namespace Service::AM {
38 42
39class IStorage; 43class IStorage;
@@ -41,6 +45,8 @@ class IStorage;
41namespace Applets { 45namespace Applets {
42 46
43enum class AppletId : u32 { 47enum class AppletId : u32 {
48 None = 0x00,
49 Application = 0x01,
44 OverlayDisplay = 0x02, 50 OverlayDisplay = 0x02,
45 QLaunch = 0x03, 51 QLaunch = 0x03,
46 Starter = 0x04, 52 Starter = 0x04,
@@ -71,6 +77,32 @@ enum class LibraryAppletMode : u32 {
71 AllForegroundInitiallyHidden = 4, 77 AllForegroundInitiallyHidden = 4,
72}; 78};
73 79
80enum class CommonArgumentVersion : u32 {
81 Version0,
82 Version1,
83 Version2,
84 Version3,
85};
86
87enum class CommonArgumentSize : u32 {
88 Version3 = 0x20,
89};
90
91enum class ThemeColor : u32 {
92 BasicWhite = 0,
93 BasicBlack = 3,
94};
95
96struct CommonArguments {
97 CommonArgumentVersion arguments_version;
98 CommonArgumentSize size;
99 u32 library_version;
100 ThemeColor theme_color;
101 bool play_startup_sound;
102 u64_le system_tick;
103};
104static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
105
74class AppletDataBroker final { 106class AppletDataBroker final {
75public: 107public:
76 explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_); 108 explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_);
@@ -161,16 +193,6 @@ public:
161 } 193 }
162 194
163protected: 195protected:
164 struct CommonArguments {
165 u32_le arguments_version;
166 u32_le size;
167 u32_le library_version;
168 u32_le theme_color;
169 bool play_startup_sound;
170 u64_le system_tick;
171 };
172 static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
173
174 CommonArguments common_args{}; 196 CommonArguments common_args{};
175 AppletDataBroker broker; 197 AppletDataBroker broker;
176 LibraryAppletMode applet_mode; 198 LibraryAppletMode applet_mode;
@@ -219,8 +241,12 @@ public:
219 ~AppletManager(); 241 ~AppletManager();
220 242
221 const AppletFrontendSet& GetAppletFrontendSet() const; 243 const AppletFrontendSet& GetAppletFrontendSet() const;
244 NFP::CabinetMode GetCabinetMode() const;
245 AppletId GetCurrentAppletId() const;
222 246
223 void SetAppletFrontendSet(AppletFrontendSet set); 247 void SetAppletFrontendSet(AppletFrontendSet set);
248 void SetCabinetMode(NFP::CabinetMode mode);
249 void SetCurrentAppletId(AppletId applet_id);
224 void SetDefaultAppletFrontendSet(); 250 void SetDefaultAppletFrontendSet();
225 void SetDefaultAppletsIfMissing(); 251 void SetDefaultAppletsIfMissing();
226 void ClearAll(); 252 void ClearAll();
@@ -228,6 +254,9 @@ public:
228 std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const; 254 std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const;
229 255
230private: 256private:
257 AppletId current_applet_id{};
258 NFP::CabinetMode cabinet_mode{};
259
231 AppletFrontendSet frontend; 260 AppletFrontendSet frontend;
232 Core::System& system; 261 Core::System& system;
233}; 262};
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/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..286f9fd10 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -4,6 +4,7 @@
4#include "core/hle/service/caps/caps.h" 4#include "core/hle/service/caps/caps.h"
5#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_c.h" 6#include "core/hle/service/caps/caps_c.h"
7#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_sc.h" 8#include "core/hle/service/caps/caps_sc.h"
8#include "core/hle/service/caps/caps_ss.h" 9#include "core/hle/service/caps/caps_ss.h"
9#include "core/hle/service/caps/caps_su.h" 10#include "core/hle/service/caps/caps_su.h"
@@ -15,13 +16,21 @@ namespace Service::Capture {
15 16
16void LoopProcess(Core::System& system) { 17void LoopProcess(Core::System& system) {
17 auto server_manager = std::make_unique<ServerManager>(system); 18 auto server_manager = std::make_unique<ServerManager>(system);
19 auto album_manager = std::make_shared<AlbumManager>();
20
21 server_manager->RegisterNamedService(
22 "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
23 server_manager->RegisterNamedService(
24 "caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
25 server_manager->RegisterNamedService(
26 "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
27
28 server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
29 server_manager->RegisterNamedService("caps:sc",
30 std::make_shared<IScreenShotControlService>(system));
31 server_manager->RegisterNamedService("caps:su",
32 std::make_shared<IScreenShotApplicationService>(system));
18 33
19 server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
20 server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
21 server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
22 server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
23 server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
24 server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
25 ServerManager::RunServer(std::move(server_manager)); 34 ServerManager::RunServer(std::move(server_manager));
26} 35}
27 36
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index 15f0ecfaa..58e9725b8 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -3,93 +3,12 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace Core { 6namespace Core {
10class System; 7class System;
11} 8}
12 9
13namespace Service::SM {
14class ServiceManager;
15}
16
17namespace Service::Capture { 10namespace Service::Capture {
18 11
19enum class AlbumImageOrientation {
20 Orientation0 = 0,
21 Orientation1 = 1,
22 Orientation2 = 2,
23 Orientation3 = 3,
24};
25
26enum class AlbumReportOption : s32 {
27 Disable = 0,
28 Enable = 1,
29};
30
31enum class ContentType : u8 {
32 Screenshot = 0,
33 Movie = 1,
34 ExtraMovie = 3,
35};
36
37enum class AlbumStorage : u8 {
38 NAND = 0,
39 SD = 1,
40};
41
42struct AlbumFileDateTime {
43 s16 year{};
44 s8 month{};
45 s8 day{};
46 s8 hour{};
47 s8 minute{};
48 s8 second{};
49 s8 uid{};
50};
51static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
52
53struct AlbumEntry {
54 u64 size{};
55 u64 application_id{};
56 AlbumFileDateTime datetime{};
57 AlbumStorage storage{};
58 ContentType content{};
59 INSERT_PADDING_BYTES(6);
60};
61static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
62
63struct AlbumFileEntry {
64 u64 size{}; // Size of the entry
65 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
66 AlbumFileDateTime datetime{};
67 AlbumStorage storage{};
68 ContentType content{};
69 INSERT_PADDING_BYTES(5);
70 u8 unknown{1}; // Set to 1 on official SW
71};
72static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
73
74struct ApplicationAlbumEntry {
75 u64 size{}; // Size of the entry
76 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
77 AlbumFileDateTime datetime{};
78 AlbumStorage storage{};
79 ContentType content{};
80 INSERT_PADDING_BYTES(5);
81 u8 unknown{1}; // Set to 1 on official SW
82};
83static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
84
85struct ApplicationAlbumFileEntry {
86 ApplicationAlbumEntry entry{};
87 AlbumFileDateTime datetime{};
88 u64 unknown{};
89};
90static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
91 "ApplicationAlbumFileEntry has incorrect size.");
92
93void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
94 13
95} // namespace Service::Capture 14} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 44267b284..e22f72bf6 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,40 +1,26 @@
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 "common/logging/log.h"
4#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
9#include "core/hle/service/ipc_helpers.h"
5 10
6namespace Service::Capture { 11namespace Service::Capture {
7 12
8class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> { 13IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
9public: 14 std::shared_ptr<AlbumManager> album_manager)
10 explicit IAlbumAccessorSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:a"}, manager{album_manager} {
11 : ServiceFramework{system_, "IAlbumAccessorSession"} {
12 // clang-format off
13 static const FunctionInfo functions[] = {
14 {2001, nullptr, "OpenAlbumMovieReadStream"},
15 {2002, nullptr, "CloseAlbumMovieReadStream"},
16 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
17 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
18 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
19 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
20 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
21 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
22 };
23 // clang-format on
24
25 RegisterHandlers(functions);
26 }
27};
28
29CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
30 // clang-format off 16 // clang-format off
31 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
32 {0, nullptr, "GetAlbumFileCount"}, 18 {0, nullptr, "GetAlbumFileCount"},
33 {1, nullptr, "GetAlbumFileList"}, 19 {1, nullptr, "GetAlbumFileList"},
34 {2, nullptr, "LoadAlbumFile"}, 20 {2, nullptr, "LoadAlbumFile"},
35 {3, nullptr, "DeleteAlbumFile"}, 21 {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
36 {4, nullptr, "StorageCopyAlbumFile"}, 22 {4, nullptr, "StorageCopyAlbumFile"},
37 {5, nullptr, "IsAlbumMounted"}, 23 {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
38 {6, nullptr, "GetAlbumUsage"}, 24 {6, nullptr, "GetAlbumUsage"},
39 {7, nullptr, "GetAlbumFileSize"}, 25 {7, nullptr, "GetAlbumFileSize"},
40 {8, nullptr, "LoadAlbumFileThumbnail"}, 26 {8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +33,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
47 {15, nullptr, "GetAlbumUsage3"}, 33 {15, nullptr, "GetAlbumUsage3"},
48 {16, nullptr, "GetAlbumMountResult"}, 34 {16, nullptr, "GetAlbumMountResult"},
49 {17, nullptr, "GetAlbumUsage16"}, 35 {17, nullptr, "GetAlbumUsage16"},
50 {18, nullptr, "Unknown18"}, 36 {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
51 {19, nullptr, "Unknown19"}, 37 {19, nullptr, "Unknown19"},
52 {100, nullptr, "GetAlbumFileCountEx0"}, 38 {100, nullptr, "GetAlbumFileCountEx0"},
53 {101, nullptr, "GetAlbumFileListEx0"}, 39 {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
54 {202, nullptr, "SaveEditedScreenShot"}, 40 {202, nullptr, "SaveEditedScreenShot"},
55 {301, nullptr, "GetLastThumbnail"}, 41 {301, nullptr, "GetLastThumbnail"},
56 {302, nullptr, "GetLastOverlayMovieThumbnail"}, 42 {302, nullptr, "GetLastOverlayMovieThumbnail"},
57 {401, nullptr, "GetAutoSavingStorage"}, 43 {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
58 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"}, 44 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
59 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"}, 45 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
60 {1002, nullptr, "LoadAlbumScreenShotImageEx1"}, 46 {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
61 {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"}, 47 {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
62 {8001, nullptr, "ForceAlbumUnmounted"}, 48 {8001, nullptr, "ForceAlbumUnmounted"},
63 {8002, nullptr, "ResetAlbumMountStatus"}, 49 {8002, nullptr, "ResetAlbumMountStatus"},
64 {8011, nullptr, "RefreshAlbumCache"}, 50 {8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +60,199 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
74 RegisterHandlers(functions); 60 RegisterHandlers(functions);
75} 61}
76 62
77CAPS_A::~CAPS_A() = default; 63IAlbumAccessorService::~IAlbumAccessorService() = default;
64
65void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
66 IPC::RequestParser rp{ctx};
67 const auto file_id{rp.PopRaw<AlbumFileId>()};
68
69 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
70 file_id.application_id, file_id.storage, file_id.type);
71
72 Result result = manager->DeleteAlbumFile(file_id);
73 result = TranslateResult(result);
74
75 IPC::ResponseBuilder rb{ctx, 2};
76 rb.Push(result);
77}
78
79void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
80 IPC::RequestParser rp{ctx};
81 const auto storage{rp.PopEnum<AlbumStorage>()};
82
83 LOG_INFO(Service_Capture, "called, storage={}", storage);
84
85 Result result = manager->IsAlbumMounted(storage);
86 const bool is_mounted = result.IsSuccess();
87 result = TranslateResult(result);
88
89 IPC::ResponseBuilder rb{ctx, 3};
90 rb.Push(result);
91 rb.Push<u8>(is_mounted);
92}
93
94void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
95 struct UnknownBuffer {
96 INSERT_PADDING_BYTES(0x10);
97 };
98 static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
99
100 LOG_WARNING(Service_Capture, "(STUBBED) called");
101
102 std::vector<UnknownBuffer> buffer{};
103
104 if (!buffer.empty()) {
105 ctx.WriteBuffer(buffer);
106 }
107
108 IPC::ResponseBuilder rb{ctx, 3};
109 rb.Push(ResultSuccess);
110 rb.Push(static_cast<u32>(buffer.size()));
111}
112
113void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
114 IPC::RequestParser rp{ctx};
115 const auto storage{rp.PopEnum<AlbumStorage>()};
116 const auto flags{rp.Pop<u8>()};
117 const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
118
119 LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
120
121 std::vector<AlbumEntry> entries;
122 Result result = manager->GetAlbumFileList(entries, storage, flags);
123 result = TranslateResult(result);
124
125 entries.resize(std::min(album_entry_size, entries.size()));
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 3};
132 rb.Push(result);
133 rb.Push(entries.size());
134}
135
136void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
137 LOG_WARNING(Service_Capture, "(STUBBED) called");
138
139 bool is_autosaving{};
140 Result result = manager->GetAutoSavingStorage(is_autosaving);
141 result = TranslateResult(result);
142
143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(result);
145 rb.Push<u8>(is_autosaving);
146}
147
148void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
149 IPC::RequestParser rp{ctx};
150 const auto file_id{rp.PopRaw<AlbumFileId>()};
151 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
152 const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
153
154 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
155 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
156
157 std::vector<u8> image;
158 LoadAlbumScreenShotImageOutput image_output;
159 Result result =
160 manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
161 result = TranslateResult(result);
162
163 if (image.size() > image_buffer_size) {
164 result = ResultWorkMemoryError;
165 }
166
167 if (result.IsSuccess()) {
168 ctx.WriteBuffer(image_output, 0);
169 ctx.WriteBuffer(image, 1);
170 }
171
172 IPC::ResponseBuilder rb{ctx, 2};
173 rb.Push(result);
174}
175
176void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
177 IPC::RequestParser rp{ctx};
178 const auto file_id{rp.PopRaw<AlbumFileId>()};
179 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
180
181 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
182 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
183
184 std::vector<u8> image(ctx.GetWriteBufferSize(1));
185 LoadAlbumScreenShotImageOutput image_output;
186 Result result =
187 manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
188 result = TranslateResult(result);
189
190 if (result.IsSuccess()) {
191 ctx.WriteBuffer(image_output, 0);
192 ctx.WriteBuffer(image, 1);
193 }
194
195 IPC::ResponseBuilder rb{ctx, 2};
196 rb.Push(result);
197}
198
199Result IAlbumAccessorService::TranslateResult(Result in_result) {
200 if (in_result.IsSuccess()) {
201 return in_result;
202 }
203
204 if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
205 if (in_result.description - 0x514 < 100) {
206 return ResultInvalidFileData;
207 }
208 if (in_result.description - 0x5dc < 100) {
209 return ResultInvalidFileData;
210 }
211
212 if (in_result.description - 0x578 < 100) {
213 if (in_result == ResultFileCountLimit) {
214 return ResultUnknown22;
215 }
216 return ResultUnknown25;
217 }
218
219 if (in_result.raw < ResultUnknown1801.raw) {
220 if (in_result == ResultUnknown1202) {
221 return ResultUnknown810;
222 }
223 if (in_result == ResultUnknown1203) {
224 return ResultUnknown810;
225 }
226 if (in_result == ResultUnknown1701) {
227 return ResultUnknown5;
228 }
229 } else if (in_result.raw < ResultUnknown1803.raw) {
230 if (in_result == ResultUnknown1801) {
231 return ResultUnknown5;
232 }
233 if (in_result == ResultUnknown1802) {
234 return ResultUnknown6;
235 }
236 } else {
237 if (in_result == ResultUnknown1803) {
238 return ResultUnknown7;
239 }
240 if (in_result == ResultUnknown1804) {
241 return ResultOutOfRange;
242 }
243 }
244 return ResultUnknown1024;
245 }
246
247 if (in_result.module == ErrorModule::FS) {
248 if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
249 (((in_result.description - 3000) >> 3) < 0x271)) {
250 // TODO: Translate FS error
251 return in_result;
252 }
253 }
254
255 return in_result;
256}
78 257
79} // namespace Service::Capture 258} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 98a21a5ad..c90cff71e 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -10,11 +10,26 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_A final : public ServiceFramework<CAPS_A> { 15class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
15public: 16public:
16 explicit CAPS_A(Core::System& system_); 17 explicit IAlbumAccessorService(Core::System& system_,
17 ~CAPS_A() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumAccessorService() override;
20
21private:
22 void DeleteAlbumFile(HLERequestContext& ctx);
23 void IsAlbumMounted(HLERequestContext& ctx);
24 void Unknown18(HLERequestContext& ctx);
25 void GetAlbumFileListEx0(HLERequestContext& ctx);
26 void GetAutoSavingStorage(HLERequestContext& ctx);
27 void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
28 void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
29
30 Result TranslateResult(Result in_result);
31
32 std::shared_ptr<AlbumManager> manager = nullptr;
18}; 33};
19 34
20} // namespace Service::Capture 35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index fc77e35cd..1e7fe6474 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -3,53 +3,21 @@
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps_c.h" 5#include "core/hle/service/caps/caps_c.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/ipc_helpers.h" 9#include "core/hle/service/ipc_helpers.h"
7 10
8namespace Service::Capture { 11namespace Service::Capture {
9 12
10class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> { 13IAlbumControlService::IAlbumControlService(Core::System& system_,
11public: 14 std::shared_ptr<AlbumManager> album_manager)
12 explicit IAlbumControlSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:c"}, manager{album_manager} {
13 : ServiceFramework{system_, "IAlbumControlSession"} {
14 // clang-format off
15 static const FunctionInfo functions[] = {
16 {2001, nullptr, "OpenAlbumMovieReadStream"},
17 {2002, nullptr, "CloseAlbumMovieReadStream"},
18 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
19 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
20 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
21 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
22 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
23 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
24 {2401, nullptr, "OpenAlbumMovieWriteStream"},
25 {2402, nullptr, "FinishAlbumMovieWriteStream"},
26 {2403, nullptr, "CommitAlbumMovieWriteStream"},
27 {2404, nullptr, "DiscardAlbumMovieWriteStream"},
28 {2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
29 {2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
30 {2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
31 {2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
32 {2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
33 {2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
34 {2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
35 {2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
36 {2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
37 {2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
38 {2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
39 {2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
40 };
41 // clang-format on
42
43 RegisterHandlers(functions);
44 }
45};
46
47CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
48 // clang-format off 16 // clang-format off
49 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
50 {1, nullptr, "CaptureRawImage"}, 18 {1, nullptr, "CaptureRawImage"},
51 {2, nullptr, "CaptureRawImageWithTimeout"}, 19 {2, nullptr, "CaptureRawImageWithTimeout"},
52 {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"}, 20 {33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
53 {1001, nullptr, "RequestTakingScreenShot"}, 21 {1001, nullptr, "RequestTakingScreenShot"},
54 {1002, nullptr, "RequestTakingScreenShotWithTimeout"}, 22 {1002, nullptr, "RequestTakingScreenShotWithTimeout"},
55 {1011, nullptr, "NotifyTakingScreenShotRefused"}, 23 {1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
72 RegisterHandlers(functions); 40 RegisterHandlers(functions);
73} 41}
74 42
75CAPS_C::~CAPS_C() = default; 43IAlbumControlService::~IAlbumControlService() = default;
76 44
77void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) { 45void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
78 IPC::RequestParser rp{ctx}; 46 IPC::RequestParser rp{ctx};
79 const auto library_version{rp.Pop<u64>()}; 47 const auto library_version{rp.Pop<u64>()};
80 const auto applet_resource_user_id{rp.Pop<u64>()}; 48 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index 537b3a2e3..92ba242db 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -10,14 +10,18 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_C final : public ServiceFramework<CAPS_C> { 15class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
15public: 16public:
16 explicit CAPS_C(Core::System& system_); 17 explicit IAlbumControlService(Core::System& system_,
17 ~CAPS_C() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumControlService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
23
24 std::shared_ptr<AlbumManager> manager = nullptr;
21}; 25};
22 26
23} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
new file mode 100644
index 000000000..2df6a930a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,342 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <sstream>
5#include <stb_image.h>
6#include <stb_image_resize.h>
7
8#include "common/fs/file.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "core/hle/service/caps/caps_manager.h"
12#include "core/hle/service/caps/caps_result.h"
13
14namespace Service::Capture {
15
16AlbumManager::AlbumManager() {}
17
18AlbumManager::~AlbumManager() = default;
19
20Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
21 if (file_id.storage > AlbumStorage::Sd) {
22 return ResultInvalidStorage;
23 }
24
25 if (!is_mounted) {
26 return ResultIsNotMounted;
27 }
28
29 std::filesystem::path path;
30 const auto result = GetFile(path, file_id);
31
32 if (result.IsError()) {
33 return result;
34 }
35
36 if (!Common::FS::RemoveFile(path)) {
37 return ResultFileNotFound;
38 }
39
40 return ResultSuccess;
41}
42
43Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
44 if (storage > AlbumStorage::Sd) {
45 return ResultInvalidStorage;
46 }
47
48 is_mounted = true;
49
50 if (storage == AlbumStorage::Sd) {
51 FindScreenshots();
52 }
53
54 return is_mounted ? ResultSuccess : ResultIsNotMounted;
55}
56
57Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
58 u8 flags) const {
59 if (storage > AlbumStorage::Sd) {
60 return ResultInvalidStorage;
61 }
62
63 if (!is_mounted) {
64 return ResultIsNotMounted;
65 }
66
67 for (auto& [file_id, path] : album_files) {
68 if (file_id.storage != storage) {
69 continue;
70 }
71 if (out_entries.size() >= SdAlbumFileLimit) {
72 break;
73 }
74
75 const auto entry_size = Common::FS::GetSize(path);
76 out_entries.push_back({
77 .entry_size = entry_size,
78 .file_id = file_id,
79 });
80 }
81
82 return ResultSuccess;
83}
84
85Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
86 ContentType contex_type, AlbumFileDateTime start_date,
87 AlbumFileDateTime end_date, u64 aruid) const {
88 if (!is_mounted) {
89 return ResultIsNotMounted;
90 }
91
92 for (auto& [file_id, path] : album_files) {
93 if (file_id.type != contex_type) {
94 continue;
95 }
96
97 if (file_id.date > start_date) {
98 continue;
99 }
100
101 if (file_id.date < end_date) {
102 continue;
103 }
104
105 if (out_entries.size() >= SdAlbumFileLimit) {
106 break;
107 }
108
109 const auto entry_size = Common::FS::GetSize(path);
110 ApplicationAlbumFileEntry entry{.entry =
111 {
112 .size = entry_size,
113 .hash{},
114 .datetime = file_id.date,
115 .storage = file_id.storage,
116 .content = contex_type,
117 .unknown = 1,
118 },
119 .datetime = file_id.date,
120 .unknown = {}};
121 out_entries.push_back(entry);
122 }
123
124 return ResultSuccess;
125}
126
127Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
128 out_is_autosaving = false;
129 return ResultSuccess;
130}
131
132Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
133 std::vector<u8>& out_image,
134 const AlbumFileId& file_id,
135 const ScreenShotDecodeOption& decoder_options) const {
136 if (file_id.storage > AlbumStorage::Sd) {
137 return ResultInvalidStorage;
138 }
139
140 if (!is_mounted) {
141 return ResultIsNotMounted;
142 }
143
144 out_image_output = {
145 .width = 1280,
146 .height = 720,
147 .attribute =
148 {
149 .unknown_0{},
150 .orientation = AlbumImageOrientation::None,
151 .unknown_1{},
152 .unknown_2{},
153 },
154 };
155
156 std::filesystem::path path;
157 const auto result = GetFile(path, file_id);
158
159 if (result.IsError()) {
160 return result;
161 }
162
163 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
164
165 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
166 +static_cast<int>(out_image_output.height), decoder_options.flags);
167}
168
169Result AlbumManager::LoadAlbumScreenShotThumbnail(
170 LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
171 const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
172 if (file_id.storage > AlbumStorage::Sd) {
173 return ResultInvalidStorage;
174 }
175
176 if (!is_mounted) {
177 return ResultIsNotMounted;
178 }
179
180 out_image_output = {
181 .width = 320,
182 .height = 180,
183 .attribute =
184 {
185 .unknown_0{},
186 .orientation = AlbumImageOrientation::None,
187 .unknown_1{},
188 .unknown_2{},
189 },
190 };
191
192 std::filesystem::path path;
193 const auto result = GetFile(path, file_id);
194
195 if (result.IsError()) {
196 return result;
197 }
198
199 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
200
201 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
202 +static_cast<int>(out_image_output.height), decoder_options.flags);
203}
204
205Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
206 const auto file = album_files.find(file_id);
207
208 if (file == album_files.end()) {
209 return ResultFileNotFound;
210 }
211
212 out_path = file->second;
213 return ResultSuccess;
214}
215
216void AlbumManager::FindScreenshots() {
217 is_mounted = false;
218 album_files.clear();
219
220 // TODO: Swap this with a blocking operation.
221 const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
222 Common::FS::IterateDirEntries(
223 screenshots_dir,
224 [this](const std::filesystem::path& full_path) {
225 AlbumEntry entry;
226 if (GetAlbumEntry(entry, full_path).IsError()) {
227 return true;
228 }
229 while (album_files.contains(entry.file_id)) {
230 if (++entry.file_id.date.unique_id == 0) {
231 break;
232 }
233 }
234 album_files[entry.file_id] = full_path;
235 return true;
236 },
237 Common::FS::DirEntryFilter::File);
238
239 is_mounted = true;
240}
241
242Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
243 std::istringstream line_stream(path.filename().string());
244 std::string date;
245 std::string application;
246 std::string time;
247
248 // Parse filename to obtain entry properties
249 std::getline(line_stream, application, '_');
250 std::getline(line_stream, date, '_');
251 std::getline(line_stream, time, '_');
252
253 std::istringstream date_stream(date);
254 std::istringstream time_stream(time);
255 std::string year;
256 std::string month;
257 std::string day;
258 std::string hour;
259 std::string minute;
260 std::string second;
261
262 std::getline(date_stream, year, '-');
263 std::getline(date_stream, month, '-');
264 std::getline(date_stream, day, '-');
265
266 std::getline(time_stream, hour, '-');
267 std::getline(time_stream, minute, '-');
268 std::getline(time_stream, second, '-');
269
270 try {
271 out_entry = {
272 .entry_size = 1,
273 .file_id{
274 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
275 .date =
276 {
277 .year = static_cast<u16>(std::stoi(year)),
278 .month = static_cast<u8>(std::stoi(month)),
279 .day = static_cast<u8>(std::stoi(day)),
280 .hour = static_cast<u8>(std::stoi(hour)),
281 .minute = static_cast<u8>(std::stoi(minute)),
282 .second = static_cast<u8>(std::stoi(second)),
283 .unique_id = 0,
284 },
285 .storage = AlbumStorage::Sd,
286 .type = ContentType::Screenshot,
287 .unknown = 1,
288 },
289 };
290 } catch (const std::invalid_argument&) {
291 return ResultUnknown;
292 } catch (const std::out_of_range&) {
293 return ResultUnknown;
294 } catch (const std::exception&) {
295 return ResultUnknown;
296 }
297
298 return ResultSuccess;
299}
300
301Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
302 int width, int height, ScreenShotDecoderFlag flag) const {
303 if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
304 return ResultUnknown;
305 }
306
307 const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
308 Common::FS::FileType::BinaryFile};
309
310 std::vector<u8> raw_file(db_file.GetSize());
311 if (db_file.Read(raw_file) != raw_file.size()) {
312 return ResultUnknown;
313 }
314
315 int filter_flag = STBIR_FILTER_DEFAULT;
316 int original_width, original_height, color_channels;
317 const auto dbi_image =
318 stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
319 &original_height, &color_channels, STBI_rgb_alpha);
320
321 if (dbi_image == nullptr) {
322 return ResultUnknown;
323 }
324
325 switch (flag) {
326 case ScreenShotDecoderFlag::EnableFancyUpsampling:
327 filter_flag = STBIR_FILTER_TRIANGLE;
328 break;
329 case ScreenShotDecoderFlag::EnableBlockSmoothing:
330 filter_flag = STBIR_FILTER_BOX;
331 break;
332 default:
333 filter_flag = STBIR_FILTER_DEFAULT;
334 break;
335 }
336
337 stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
338 height, 0, STBI_rgb_alpha, 3, filter_flag);
339
340 return ResultSuccess;
341}
342} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
new file mode 100644
index 000000000..8337c655c
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <unordered_map>
7
8#include "common/fs/fs.h"
9#include "core/hle/result.h"
10#include "core/hle/service/caps/caps_types.h"
11
12namespace Core {
13class System;
14}
15
16namespace std {
17// Hash used to create lists from AlbumFileId data
18template <>
19struct hash<Service::Capture::AlbumFileId> {
20 size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
21 u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
22 hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
23 hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
24 hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
25 hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
26 hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
27 hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
28 hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
29 hash_value ^= static_cast<u64>(pad_id.type);
30 return static_cast<size_t>(hash_value);
31 }
32};
33
34} // namespace std
35
36namespace Service::Capture {
37
38class AlbumManager {
39public:
40 explicit AlbumManager();
41 ~AlbumManager();
42
43 Result DeleteAlbumFile(const AlbumFileId& file_id);
44 Result IsAlbumMounted(AlbumStorage storage);
45 Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
46 u8 flags) const;
47 Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
48 ContentType contex_type, AlbumFileDateTime start_date,
49 AlbumFileDateTime end_date, u64 aruid) const;
50 Result GetAutoSavingStorage(bool& out_is_autosaving) const;
51 Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
52 std::vector<u8>& out_image, const AlbumFileId& file_id,
53 const ScreenShotDecodeOption& decoder_options) const;
54 Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
55 std::vector<u8>& out_image, const AlbumFileId& file_id,
56 const ScreenShotDecodeOption& decoder_options) const;
57
58private:
59 static constexpr std::size_t NandAlbumFileLimit = 1000;
60 static constexpr std::size_t SdAlbumFileLimit = 10000;
61
62 void FindScreenshots();
63 Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
64 Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
65 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
66 int height, ScreenShotDecoderFlag flag) const;
67
68 bool is_mounted{};
69 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
70};
71
72} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h
new file mode 100644
index 000000000..c65e5fb9a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_result.h
@@ -0,0 +1,35 @@
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
8namespace Service::Capture {
9
10constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
11constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
12constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
13constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
14constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
15constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
16constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
17constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
18constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
19constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
20constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
21constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
22constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
23constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
24constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
25constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
26constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
27constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
28constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
29constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
30constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
31constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
32constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
33constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
34
35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index 395b13da7..6117cb7c6 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} { 8IScreenShotControlService::IScreenShotControlService(Core::System& system_)
9 : ServiceFramework{system_, "caps:sc"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {1, nullptr, "CaptureRawImage"}, 12 {1, nullptr, "CaptureRawImage"},
@@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
34 RegisterHandlers(functions); 35 RegisterHandlers(functions);
35} 36}
36 37
37CAPS_SC::~CAPS_SC() = default; 38IScreenShotControlService::~IScreenShotControlService() = default;
38 39
39} // namespace Service::Capture 40} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index e5600f6d7..d555f4979 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SC final : public ServiceFramework<CAPS_SC> { 14class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
15public: 15public:
16 explicit CAPS_SC(Core::System& system_); 16 explicit IScreenShotControlService(Core::System& system_);
17 ~CAPS_SC() override; 17 ~IScreenShotControlService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 62b9edd41..d0d1b5425 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { 8IScreenShotService::IScreenShotService(Core::System& system_)
9 : ServiceFramework{system_, "caps:ss"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {201, nullptr, "SaveScreenShot"}, 12 {201, nullptr, "SaveScreenShot"},
@@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SS::~CAPS_SS() = default; 25IScreenShotService::~IScreenShotService() = default;
25 26
26} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 718ade485..381e44fd4 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SS final : public ServiceFramework<CAPS_SS> { 14class IScreenShotService final : public ServiceFramework<IScreenShotService> {
15public: 15public:
16 explicit CAPS_SS(Core::System& system_); 16 explicit IScreenShotService(Core::System& system_);
17 ~CAPS_SS() override; 17 ~IScreenShotService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 3b11cc95c..cad173dc7 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -7,10 +7,11 @@
7 7
8namespace Service::Capture { 8namespace Service::Capture {
9 9
10CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { 10IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
11 : ServiceFramework{system_, "caps:su"} {
11 // clang-format off 12 // clang-format off
12 static const FunctionInfo functions[] = { 13 static const FunctionInfo functions[] = {
13 {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, 14 {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
14 {201, nullptr, "SaveScreenShot"}, 15 {201, nullptr, "SaveScreenShot"},
15 {203, nullptr, "SaveScreenShotEx0"}, 16 {203, nullptr, "SaveScreenShotEx0"},
16 {205, nullptr, "SaveScreenShotEx1"}, 17 {205, nullptr, "SaveScreenShotEx1"},
@@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SU::~CAPS_SU() = default; 25IScreenShotApplicationService::~IScreenShotApplicationService() = default;
25 26
26void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) { 27void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
27 IPC::RequestParser rp{ctx}; 28 IPC::RequestParser rp{ctx};
28 const auto library_version{rp.Pop<u64>()}; 29 const auto library_version{rp.Pop<u64>()};
29 const auto applet_resource_user_id{rp.Pop<u64>()}; 30 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index c6398858d..647e3059d 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SU final : public ServiceFramework<CAPS_SU> { 14class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
15public: 15public:
16 explicit CAPS_SU(Core::System& system_); 16 explicit IScreenShotApplicationService(Core::System& system_);
17 ~CAPS_SU() override; 17 ~IScreenShotApplicationService() override;
18 18
19private: 19private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 20 void SetShimLibraryVersion(HLERequestContext& ctx);
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
new file mode 100644
index 000000000..bf6061273
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2018 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 Service::Capture {
10
11// This is nn::album::ImageOrientation
12enum class AlbumImageOrientation {
13 None,
14 Rotate90,
15 Rotate180,
16 Rotate270,
17};
18
19// This is nn::album::AlbumReportOption
20enum class AlbumReportOption : s32 {
21 Disable,
22 Enable,
23};
24
25enum class ContentType : u8 {
26 Screenshot = 0,
27 Movie = 1,
28 ExtraMovie = 3,
29};
30
31enum class AlbumStorage : u8 {
32 Nand,
33 Sd,
34};
35
36enum class ScreenShotDecoderFlag : u64 {
37 None = 0,
38 EnableFancyUpsampling = 1 << 0,
39 EnableBlockSmoothing = 1 << 1,
40};
41
42// This is nn::capsrv::AlbumFileDateTime
43struct AlbumFileDateTime {
44 u16 year{};
45 u8 month{};
46 u8 day{};
47 u8 hour{};
48 u8 minute{};
49 u8 second{};
50 u8 unique_id{};
51
52 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
53 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
54 if (a.year > b.year) {
55 return true;
56 }
57 if (a.month > b.month) {
58 return true;
59 }
60 if (a.day > b.day) {
61 return true;
62 }
63 if (a.hour > b.hour) {
64 return true;
65 }
66 if (a.minute > b.minute) {
67 return true;
68 }
69 return a.second > b.second;
70 };
71 friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
72 if (a.year < b.year) {
73 return true;
74 }
75 if (a.month < b.month) {
76 return true;
77 }
78 if (a.day < b.day) {
79 return true;
80 }
81 if (a.hour < b.hour) {
82 return true;
83 }
84 if (a.minute < b.minute) {
85 return true;
86 }
87 return a.second < b.second;
88 };
89};
90static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
91
92// This is nn::album::AlbumEntry
93struct AlbumFileEntry {
94 u64 size{}; // Size of the entry
95 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
96 AlbumFileDateTime datetime{};
97 AlbumStorage storage{};
98 ContentType content{};
99 INSERT_PADDING_BYTES(5);
100 u8 unknown{}; // Set to 1 on official SW
101};
102static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
103
104struct AlbumFileId {
105 u64 application_id{};
106 AlbumFileDateTime date{};
107 AlbumStorage storage{};
108 ContentType type{};
109 INSERT_PADDING_BYTES(0x5);
110 u8 unknown{};
111
112 friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
113};
114static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
115
116// This is nn::capsrv::AlbumEntry
117struct AlbumEntry {
118 u64 entry_size{};
119 AlbumFileId file_id{};
120};
121static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
122
123// This is nn::capsrv::ApplicationAlbumEntry
124struct ApplicationAlbumEntry {
125 u64 size{}; // Size of the entry
126 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
127 AlbumFileDateTime datetime{};
128 AlbumStorage storage{};
129 ContentType content{};
130 INSERT_PADDING_BYTES(5);
131 u8 unknown{1}; // Set to 1 on official SW
132};
133static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
134
135// This is nn::capsrv::ApplicationAlbumFileEntry
136struct ApplicationAlbumFileEntry {
137 ApplicationAlbumEntry entry{};
138 AlbumFileDateTime datetime{};
139 u64 unknown{};
140};
141static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
142 "ApplicationAlbumFileEntry has incorrect size.");
143
144struct ApplicationData {
145 std::array<u8, 0x400> data{};
146 u32 data_size{};
147};
148static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
149
150struct ScreenShotAttribute {
151 u32 unknown_0{};
152 AlbumImageOrientation orientation{};
153 u32 unknown_1{};
154 u32 unknown_2{};
155 INSERT_PADDING_BYTES(0x30);
156};
157static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
158
159struct ScreenShotDecodeOption {
160 ScreenShotDecoderFlag flags{};
161 INSERT_PADDING_BYTES(0x18);
162};
163static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
164
165struct LoadAlbumScreenShotImageOutput {
166 s64 width{};
167 s64 height{};
168 ScreenShotAttribute attribute{};
169 INSERT_PADDING_BYTES(0x400);
170};
171static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
172 "LoadAlbumScreenShotImageOutput is an invalid size");
173
174struct LoadAlbumScreenShotImageOutputForApplication {
175 s64 width{};
176 s64 height{};
177 ScreenShotAttribute attribute{};
178 ApplicationData data{};
179 INSERT_PADDING_BYTES(0xAC);
180};
181static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
182 "LoadAlbumScreenShotImageOutput is an invalid size");
183
184} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index bffe0f8d0..260f25490 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -2,45 +2,29 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps.h" 5#include "core/hle/service/caps/caps_manager.h"
6#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/caps/caps_u.h" 7#include "core/hle/service/caps/caps_u.h"
7#include "core/hle/service/ipc_helpers.h" 8#include "core/hle/service/ipc_helpers.h"
8 9
9namespace Service::Capture { 10namespace Service::Capture {
10 11
11class IAlbumAccessorApplicationSession final 12IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
12 : public ServiceFramework<IAlbumAccessorApplicationSession> { 13 std::shared_ptr<AlbumManager> album_manager)
13public: 14 : ServiceFramework{system_, "caps:u"}, manager{album_manager} {
14 explicit IAlbumAccessorApplicationSession(Core::System& system_)
15 : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {2001, nullptr, "OpenAlbumMovieReadStream"},
19 {2002, nullptr, "CloseAlbumMovieReadStream"},
20 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
21 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
22 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
23 };
24 // clang-format on
25
26 RegisterHandlers(functions);
27 }
28};
29
30CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
31 // clang-format off 15 // clang-format off
32 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
33 {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"}, 17 {32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
34 {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"}, 18 {102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
35 {103, nullptr, "DeleteAlbumContentsFileForApplication"}, 19 {103, nullptr, "DeleteAlbumFileByAruid"},
36 {104, nullptr, "GetAlbumContentsFileSizeForApplication"}, 20 {104, nullptr, "GetAlbumFileSizeByAruid"},
37 {105, nullptr, "DeleteAlbumFileByAruidForDebug"}, 21 {105, nullptr, "DeleteAlbumFileByAruidForDebug"},
38 {110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"}, 22 {110, nullptr, "LoadAlbumScreenShotImageByAruid"},
39 {120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"}, 23 {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
40 {130, nullptr, "PrecheckToCreateContentsForApplication"}, 24 {130, nullptr, "PrecheckToCreateContentsByAruid"},
41 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, 25 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
42 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, 26 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
43 {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"}, 27 {142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
44 {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, 28 {143, nullptr, "GetAlbumFileList4AaeUidAruid"},
45 {144, nullptr, "GetAllAlbumFileList3AaeAruid"}, 29 {144, nullptr, "GetAllAlbumFileList3AaeAruid"},
46 {60002, nullptr, "OpenAccessorSessionForApplication"}, 30 {60002, nullptr, "OpenAccessorSessionForApplication"},
@@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
50 RegisterHandlers(functions); 34 RegisterHandlers(functions);
51} 35}
52 36
53CAPS_U::~CAPS_U() = default; 37IAlbumApplicationService::~IAlbumApplicationService() = default;
54 38
55void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) { 39void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
56 IPC::RequestParser rp{ctx}; 40 IPC::RequestParser rp{ctx};
57 const auto library_version{rp.Pop<u64>()}; 41 const auto library_version{rp.Pop<u64>()};
58 const auto applet_resource_user_id{rp.Pop<u64>()}; 42 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -64,10 +48,7 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
64 rb.Push(ResultSuccess); 48 rb.Push(ResultSuccess);
65} 49}
66 50
67void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) { 51void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
68 // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
69 // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
70 // output entries (which is copied to a s32 by official SW).
71 IPC::RequestParser rp{ctx}; 52 IPC::RequestParser rp{ctx};
72 const auto pid{rp.Pop<s32>()}; 53 const auto pid{rp.Pop<s32>()};
73 const auto content_type{rp.PopEnum<ContentType>()}; 54 const auto content_type{rp.PopEnum<ContentType>()};
@@ -75,26 +56,49 @@ void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
75 const auto end_posix_time{rp.Pop<s64>()}; 56 const auto end_posix_time{rp.Pop<s64>()};
76 const auto applet_resource_user_id{rp.Pop<u64>()}; 57 const auto applet_resource_user_id{rp.Pop<u64>()};
77 58
78 // TODO: Update this when we implement the album. 59 LOG_WARNING(Service_Capture,
79 // Currently we do not have a method of accessing album entries, set this to 0 for now. 60 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
80 constexpr u32 total_entries_1{}; 61 "end_posix_time={}, applet_resource_user_id={}",
81 constexpr u32 total_entries_2{}; 62 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
63
64 // TODO: Translate posix to DateTime
65
66 std::vector<ApplicationAlbumFileEntry> entries;
67 const Result result =
68 manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
82 69
83 LOG_WARNING( 70 if (!entries.empty()) {
84 Service_Capture, 71 ctx.WriteBuffer(entries);
85 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " 72 }
86 "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
87 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
88 total_entries_1, total_entries_2);
89 73
90 IPC::ResponseBuilder rb{ctx, 4}; 74 IPC::ResponseBuilder rb{ctx, 4};
91 rb.Push(ResultSuccess); 75 rb.Push(result);
92 rb.Push(total_entries_1); 76 rb.Push<u64>(entries.size());
93 rb.Push(total_entries_2);
94} 77}
95 78
96void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { 79void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
97 GetAlbumContentsFileListForApplication(ctx); 80 IPC::RequestParser rp{ctx};
81 const auto pid{rp.Pop<s32>()};
82 const auto content_type{rp.PopEnum<ContentType>()};
83 const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
84 const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
85 const auto applet_resource_user_id{rp.Pop<u64>()};
86
87 LOG_WARNING(Service_Capture,
88 "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
89 content_type, applet_resource_user_id);
90
91 std::vector<ApplicationAlbumFileEntry> entries;
92 const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
93 end_date_time, applet_resource_user_id);
94
95 if (!entries.empty()) {
96 ctx.WriteBuffer(entries);
97 }
98
99 IPC::ResponseBuilder rb{ctx, 4};
100 rb.Push(result);
101 rb.Push<u64>(entries.size());
98} 102}
99 103
100} // namespace Service::Capture 104} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e8dd037d7..9458c128e 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -10,16 +10,20 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_U final : public ServiceFramework<CAPS_U> { 15class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
15public: 16public:
16 explicit CAPS_U(Core::System& system_); 17 explicit IAlbumApplicationService(Core::System& system_,
17 ~CAPS_U() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumApplicationService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
21 void GetAlbumContentsFileListForApplication(HLERequestContext& ctx); 23 void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
22 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx); 24 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
25
26 std::shared_ptr<AlbumManager> manager = nullptr;
23}; 27};
24 28
25} // namespace Service::Capture 29} // namespace Service::Capture
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 6e4d26b1e..126cd6ffd 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"},
@@ -814,6 +855,9 @@ FSP_SRV::FSP_SRV(Core::System& system_)
814 if (Settings::values.enable_fs_access_log) { 855 if (Settings::values.enable_fs_access_log) {
815 access_log_mode = AccessLogMode::SdCard; 856 access_log_mode = AccessLogMode::SdCard;
816 } 857 }
858
859 // This should be true on creation
860 fsc.SetAutoSaveDataCreation(true);
817} 861}
818 862
819FSP_SRV::~FSP_SRV() = default; 863FSP_SRV::~FSP_SRV() = default;
@@ -870,6 +914,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
870 rb.Push(ResultSuccess); 914 rb.Push(ResultSuccess);
871} 915}
872 916
917void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
918 IPC::RequestParser rp{ctx};
919
920 auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
921 [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
922
923 LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
924
925 FileSys::VirtualDir save_data_dir{};
926 fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
927
928 IPC::ResponseBuilder rb{ctx, 2};
929 rb.Push(ResultSuccess);
930}
931
873void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { 932void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
874 IPC::RequestParser rp{ctx}; 933 IPC::RequestParser rp{ctx};
875 934
@@ -916,6 +975,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
916 rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); 975 rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
917} 976}
918 977
978void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
979 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
980 OpenSaveDataFileSystem(ctx);
981}
982
919void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { 983void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
920 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); 984 LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
921 OpenSaveDataFileSystem(ctx); 985 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 146bb486d..bc822f19e 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -346,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
346 } 346 }
347 SignalStyleSetChangedEvent(npad_id); 347 SignalStyleSetChangedEvent(npad_id);
348 WriteEmptyEntry(controller.shared_memory); 348 WriteEmptyEntry(controller.shared_memory);
349 hid_core.SetLastActiveController(npad_id);
349} 350}
350 351
351void Controller_NPad::OnInit() { 352void Controller_NPad::OnInit() {
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 9d149a7cd..7927f8264 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -23,19 +23,39 @@ public:
23 explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} { 23 explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
24 // clang-format off 24 // clang-format off
25 static const FunctionInfo functions[] = { 25 static const FunctionInfo functions[] = {
26 {0, nullptr, "GetStateForMonitor"}, 26 {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
27 {1, nullptr, "GetNetworkInfoForMonitor"}, 27 {1, nullptr, "GetNetworkInfoForMonitor"},
28 {2, nullptr, "GetIpv4AddressForMonitor"}, 28 {2, nullptr, "GetIpv4AddressForMonitor"},
29 {3, nullptr, "GetDisconnectReasonForMonitor"}, 29 {3, nullptr, "GetDisconnectReasonForMonitor"},
30 {4, nullptr, "GetSecurityParameterForMonitor"}, 30 {4, nullptr, "GetSecurityParameterForMonitor"},
31 {5, nullptr, "GetNetworkConfigForMonitor"}, 31 {5, nullptr, "GetNetworkConfigForMonitor"},
32 {100, nullptr, "InitializeMonitor"}, 32 {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
33 {101, nullptr, "FinalizeMonitor"}, 33 {101, nullptr, "FinalizeMonitor"},
34 }; 34 };
35 // clang-format on 35 // clang-format on
36 36
37 RegisterHandlers(functions); 37 RegisterHandlers(functions);
38 } 38 }
39
40private:
41 void GetStateForMonitor(HLERequestContext& ctx) {
42 LOG_INFO(Service_LDN, "called");
43
44 IPC::ResponseBuilder rb{ctx, 3};
45 rb.Push(ResultSuccess);
46 rb.PushEnum(state);
47 }
48
49 void InitializeMonitor(HLERequestContext& ctx) {
50 LOG_INFO(Service_LDN, "called");
51
52 state = State::Initialized;
53
54 IPC::ResponseBuilder rb{ctx, 2};
55 rb.Push(ResultSuccess);
56 }
57
58 State state{State::None};
39}; 59};
40 60
41class LDNM final : public ServiceFramework<LDNM> { 61class LDNM final : public ServiceFramework<LDNM> {
@@ -731,14 +751,81 @@ public:
731 } 751 }
732}; 752};
733 753
754class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
755public:
756 explicit ISfMonitorService(Core::System& system_)
757 : ServiceFramework{system_, "ISfMonitorService"} {
758 // clang-format off
759 static const FunctionInfo functions[] = {
760 {0, &ISfMonitorService::Initialize, "Initialize"},
761 {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
762 {320, nullptr, "GetLinkLevel"},
763 };
764 // clang-format on
765
766 RegisterHandlers(functions);
767 }
768
769private:
770 void Initialize(HLERequestContext& ctx) {
771 LOG_WARNING(Service_LDN, "(STUBBED) called");
772
773 IPC::ResponseBuilder rb{ctx, 3};
774 rb.Push(ResultSuccess);
775 rb.Push(0);
776 }
777
778 void GetGroupInfo(HLERequestContext& ctx) {
779 LOG_WARNING(Service_LDN, "(STUBBED) called");
780
781 struct GroupInfo {
782 std::array<u8, 0x200> info;
783 };
784
785 GroupInfo group_info{};
786
787 ctx.WriteBuffer(group_info);
788 IPC::ResponseBuilder rb{ctx, 2};
789 rb.Push(ResultSuccess);
790 }
791};
792
793class LP2PM final : public ServiceFramework<LP2PM> {
794public:
795 explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
796 // clang-format off
797 static const FunctionInfo functions[] = {
798 {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
799 };
800 // clang-format on
801
802 RegisterHandlers(functions);
803 }
804
805private:
806 void CreateMonitorService(HLERequestContext& ctx) {
807 IPC::RequestParser rp{ctx};
808 const u64 reserved_input = rp.Pop<u64>();
809
810 LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
811
812 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
813 rb.Push(ResultSuccess);
814 rb.PushIpcInterface<ISfMonitorService>(system);
815 }
816};
817
734void LoopProcess(Core::System& system) { 818void LoopProcess(Core::System& system) {
735 auto server_manager = std::make_unique<ServerManager>(system); 819 auto server_manager = std::make_unique<ServerManager>(system);
736 820
737 server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system)); 821 server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
738 server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system)); 822 server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
739 server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system)); 823 server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
824
740 server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system)); 825 server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
741 server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system)); 826 server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
827 server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
828
742 ServerManager::RunServer(std::move(server_manager)); 829 ServerManager::RunServer(std::move(server_manager));
743} 830}
744 831
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 611ff4f81..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,12 +600,12 @@ 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 or dirty strings 610 // Checks for null or dirty strings
610 bool IsValid() const { 611 bool IsValid() const {
@@ -613,7 +614,7 @@ struct Nickname {
613 } 614 }
614 615
615 std::size_t index = 1; 616 std::size_t index = 1;
616 while (data[index] != 0) { 617 while (index < MaxNameSize && data[index] != 0) {
617 index++; 618 index++;
618 } 619 }
619 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 674d2e4b2..e7a00deb3 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,
@@ -828,11 +830,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
828 return ResultWrongDeviceState; 830 return ResultWrongDeviceState;
829 } 831 }
830 832
831 Service::Mii::StoreData store_data{};
832 Service::Mii::NfpStoreDataExtension extension{};
833 store_data.BuildBase(Mii::Gender::Male);
834 extension.SetFromStoreData(store_data);
835
836 auto& settings = tag_data.settings; 833 auto& settings = tag_data.settings;
837 834
838 if (tag_data.settings.settings.amiibo_initialized == 0) { 835 if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -841,8 +838,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
841 } 838 }
842 839
843 SetAmiiboName(settings, register_info.amiibo_name); 840 SetAmiiboName(settings, register_info.amiibo_name);
844 tag_data.owner_mii.BuildFromStoreData(store_data); 841 tag_data.owner_mii.BuildFromStoreData(register_info.mii_store_data);
845 tag_data.mii_extension = extension; 842 tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data);
846 tag_data.unknown = 0; 843 tag_data.unknown = 0;
847 tag_data.unknown2 = {}; 844 tag_data.unknown2 = {};
848 settings.country_code_id = 0; 845 settings.country_code_id = 0;
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 21b06d10b..22dc55a6d 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -545,6 +545,16 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) {
545 } 545 }
546} 546}
547 547
548void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
549 const bool is_accepted{};
550
551 LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
552
553 IPC::ResponseBuilder rb{ctx, 3};
554 rb.Push(ResultSuccess);
555 rb.Push<u8>(is_accepted);
556}
557
548IGeneralService::IGeneralService(Core::System& system_) 558IGeneralService::IGeneralService(Core::System& system_)
549 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { 559 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
550 // clang-format off 560 // clang-format off
@@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
569 {19, nullptr, "SetEthernetCommunicationEnabled"}, 579 {19, nullptr, "SetEthernetCommunicationEnabled"},
570 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"}, 580 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
571 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"}, 581 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
572 {22, nullptr, "IsAnyForegroundRequestAccepted"}, 582 {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
573 {23, nullptr, "PutToSleep"}, 583 {23, nullptr, "PutToSleep"},
574 {24, nullptr, "WakeUp"}, 584 {24, nullptr, "WakeUp"},
575 {25, nullptr, "GetSsidListVersion"}, 585 {25, nullptr, "GetSsidListVersion"},
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index ae99c4695..b74b66438 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -35,6 +35,7 @@ private:
35 void GetInternetConnectionStatus(HLERequestContext& ctx); 35 void GetInternetConnectionStatus(HLERequestContext& ctx);
36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx); 36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx); 37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
38 void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
38 39
39 Network::RoomNetwork& network; 40 Network::RoomNetwork& network;
40}; 41};
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/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 6e0baf0be..f9e0e272d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,6 +7,7 @@
7#include "core/file_sys/control_metadata.h" 7#include "core/file_sys/control_metadata.h"
8#include "core/file_sys/patch_manager.h" 8#include "core/file_sys/patch_manager.h"
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/hle/service/filesystem/filesystem.h"
10#include "core/hle/service/glue/glue_manager.h" 11#include "core/hle/service/glue/glue_manager.h"
11#include "core/hle/service/ipc_helpers.h" 12#include "core/hle/service/ipc_helpers.h"
12#include "core/hle/service/ns/errors.h" 13#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
502 static const FunctionInfo functions[] = { 503 static const FunctionInfo functions[] = {
503 {11, nullptr, "CalculateApplicationOccupiedSize"}, 504 {11, nullptr, "CalculateApplicationOccupiedSize"},
504 {43, nullptr, "CheckSdCardMountStatus"}, 505 {43, nullptr, "CheckSdCardMountStatus"},
505 {47, nullptr, "GetTotalSpaceSize"}, 506 {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
506 {48, nullptr, "GetFreeSpaceSize"}, 507 {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
507 {600, nullptr, "CountApplicationContentMeta"}, 508 {600, nullptr, "CountApplicationContentMeta"},
508 {601, nullptr, "ListApplicationContentMetaStatus"}, 509 {601, nullptr, "ListApplicationContentMetaStatus"},
509 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, 510 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
516 517
517IContentManagementInterface::~IContentManagementInterface() = default; 518IContentManagementInterface::~IContentManagementInterface() = default;
518 519
520void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
521 IPC::RequestParser rp{ctx};
522 const auto storage{rp.PopEnum<FileSys::StorageId>()};
523
524 LOG_INFO(Service_Capture, "called, storage={}", storage);
525
526 IPC::ResponseBuilder rb{ctx, 4};
527 rb.Push(ResultSuccess);
528 rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
529}
530
531void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
532 IPC::RequestParser rp{ctx};
533 const auto storage{rp.PopEnum<FileSys::StorageId>()};
534
535 LOG_INFO(Service_Capture, "called, storage={}", storage);
536
537 IPC::ResponseBuilder rb{ctx, 4};
538 rb.Push(ResultSuccess);
539 rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
540}
541
519IDocumentInterface::IDocumentInterface(Core::System& system_) 542IDocumentInterface::IDocumentInterface(Core::System& system_)
520 : ServiceFramework{system_, "IDocumentInterface"} { 543 : ServiceFramework{system_, "IDocumentInterface"} {
521 // clang-format off 544 // clang-format off
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 175dad780..34d2a45dc 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage
48public: 48public:
49 explicit IContentManagementInterface(Core::System& system_); 49 explicit IContentManagementInterface(Core::System& system_);
50 ~IContentManagementInterface() override; 50 ~IContentManagementInterface() override;
51
52private:
53 void GetTotalSpaceSize(HLERequestContext& ctx);
54 void GetFreeSpaceSize(HLERequestContext& ctx);
51}; 55};
52 56
53class IDocumentInterface final : public ServiceFramework<IDocumentInterface> { 57class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 40c65b430..4c0cc71cd 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -45,13 +45,6 @@ public:
45 IsSharedMemMapped = 6 45 IsSharedMemMapped = 6
46 }; 46 };
47 47
48private:
49 /// Id to use for the next handle that is created.
50 u32 next_handle = 0;
51
52 /// Id to use for the next object that is created.
53 u32 next_id = 0;
54
55 struct IocCreateParams { 48 struct IocCreateParams {
56 // Input 49 // Input
57 u32_le size{}; 50 u32_le size{};
@@ -113,6 +106,13 @@ private:
113 NvResult IocParam(std::span<const u8> input, std::span<u8> output); 106 NvResult IocParam(std::span<const u8> input, std::span<u8> output);
114 NvResult IocFree(std::span<const u8> input, std::span<u8> output); 107 NvResult IocFree(std::span<const u8> input, std::span<u8> output);
115 108
109private:
110 /// Id to use for the next handle that is created.
111 u32 next_handle = 0;
112
113 /// Id to use for the next object that is created.
114 u32 next_id = 0;
115
116 NvCore::Container& container; 116 NvCore::Container& container;
117 NvCore::NvMap& file; 117 NvCore::NvMap& file;
118}; 118};
diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h
index 7fd808f54..3da8cc3aa 100644
--- a/src/core/hle/service/nvnflinger/buffer_item.h
+++ b/src/core/hle/service/nvnflinger/buffer_item.h
@@ -15,7 +15,7 @@
15 15
16namespace Service::android { 16namespace Service::android {
17 17
18class GraphicBuffer; 18struct GraphicBuffer;
19 19
20class BufferItem final { 20class BufferItem final {
21public: 21public:
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d25bca049..d8c9dec3b 100644
--- a/src/core/hle/service/nvnflinger/buffer_slot.h
+++ b/src/core/hle/service/nvnflinger/buffer_slot.h
@@ -13,7 +13,7 @@
13 13
14namespace Service::android { 14namespace Service::android {
15 15
16class GraphicBuffer; 16struct GraphicBuffer;
17 17
18enum class BufferState : u32 { 18enum class BufferState : u32 {
19 Free = 0, 19 Free = 0,
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
new file mode 100644
index 000000000..469a53244
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -0,0 +1,351 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <random>
5
6#include "core/core.h"
7#include "core/hle/kernel/k_process.h"
8#include "core/hle/kernel/k_system_resource.h"
9#include "core/hle/service/nvdrv/devices/nvmap.h"
10#include "core/hle/service/nvdrv/nvdrv.h"
11#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
12#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
13#include "core/hle/service/nvnflinger/pixel_format.h"
14#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
15#include "core/hle/service/vi/layer/vi_layer.h"
16#include "core/hle/service/vi/vi_results.h"
17
18namespace Service::Nvnflinger {
19
20namespace {
21
22Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
23 std::unique_ptr<Kernel::KPageGroup>* out_page_group,
24 Core::System& system, u32 size) {
25 using Core::Memory::YUZU_PAGESIZE;
26
27 // Allocate memory for the system shared buffer.
28 // FIXME: Because the gmmu can only point to cpu addresses, we need
29 // to map this in the application space to allow it to be used.
30 // FIXME: Add proper smmu emulation.
31 // FIXME: This memory belongs to vi's .data section.
32 auto& kernel = system.Kernel();
33 auto* process = system.ApplicationProcess();
34 auto& page_table = process->GetPageTable();
35
36 // Hold a temporary page group reference while we try to map it.
37 auto pg = std::make_unique<Kernel::KPageGroup>(
38 kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager()));
39
40 // Allocate memory from secure pool.
41 R_TRY(kernel.MemoryManager().AllocateAndOpen(
42 pg.get(), size / YUZU_PAGESIZE,
43 Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
44 Kernel::KMemoryManager::Direction::FromBack)));
45
46 // Get bounds of where mapping is possible.
47 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
48 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
49 const auto state = Kernel::KMemoryState::Io;
50 const auto perm = Kernel::KMemoryPermission::UserReadWrite;
51 std::mt19937_64 rng{process->GetRandomEntropy(0)};
52
53 // Retry up to 64 times to map into alias code range.
54 Result res = ResultSuccess;
55 int i;
56 for (i = 0; i < 64; i++) {
57 *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE);
58 res = page_table.MapPageGroup(*out_map_address, *pg, state, perm);
59 if (R_SUCCEEDED(res)) {
60 break;
61 }
62 }
63
64 // Return failure, if necessary
65 R_UNLESS(i < 64, res);
66
67 // Return the mapped page group.
68 *out_page_group = std::move(pg);
69
70 // We succeeded.
71 R_SUCCEED();
72}
73
74template <typename T>
75std::span<u8> SerializeIoc(T& params) {
76 return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T));
77}
78
79Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) {
80 // Create a handle.
81 Nvidia::Devices::nvmap::IocCreateParams create_in_params{
82 .size = size,
83 .handle = 0,
84 };
85 Nvidia::Devices::nvmap::IocCreateParams create_out_params{};
86 R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) ==
87 Nvidia::NvResult::Success,
88 VI::ResultOperationFailed);
89
90 // Assign the output handle.
91 *out_nv_map_handle = create_out_params.handle;
92
93 // We succeeded.
94 R_SUCCEED();
95}
96
97Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
98 // Free the handle.
99 Nvidia::Devices::nvmap::IocFreeParams free_in_params{
100 .handle = handle,
101 };
102 Nvidia::Devices::nvmap::IocFreeParams free_out_params{};
103 R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) ==
104 Nvidia::NvResult::Success,
105 VI::ResultOperationFailed);
106
107 // We succeeded.
108 R_SUCCEED();
109}
110
111Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer,
112 u32 size) {
113 // Assign the allocated memory to the handle.
114 Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{
115 .handle = handle,
116 .heap_mask = 0,
117 .flags = {},
118 .align = 0,
119 .kind = 0,
120 .address = GetInteger(buffer),
121 };
122 Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{};
123 R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) ==
124 Nvidia::NvResult::Success,
125 VI::ResultOperationFailed);
126
127 // We succeeded.
128 R_SUCCEED();
129}
130
131Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv,
132 Common::ProcessAddress buffer, u32 size) {
133 // Get the nvmap device.
134 auto nvmap_fd = nvdrv.Open("/dev/nvmap");
135 auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
136 ASSERT(nvmap != nullptr);
137
138 // Create a handle.
139 R_TRY(CreateNvMapHandle(out_handle, *nvmap, size));
140
141 // Ensure we maintain a clean state on failure.
142 ON_RESULT_FAILURE {
143 ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle)));
144 };
145
146 // Assign the allocated memory to the handle.
147 R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size));
148}
149
150constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
151constexpr u32 SharedBufferBlockLinearBpp = 4;
152
153constexpr u32 SharedBufferBlockLinearWidth = 1280;
154constexpr u32 SharedBufferBlockLinearHeight = 768;
155constexpr u32 SharedBufferBlockLinearStride =
156 SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp;
157constexpr u32 SharedBufferNumSlots = 7;
158
159constexpr u32 SharedBufferWidth = 1280;
160constexpr u32 SharedBufferHeight = 720;
161constexpr u32 SharedBufferAsync = false;
162
163constexpr u32 SharedBufferSlotSize =
164 SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp;
165constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots;
166
167constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
168 SharedMemoryPoolLayout layout{};
169 layout.num_slots = SharedBufferNumSlots;
170
171 for (u32 i = 0; i < SharedBufferNumSlots; i++) {
172 layout.slots[i].buffer_offset = i * SharedBufferSlotSize;
173 layout.slots[i].size = SharedBufferSlotSize;
174 layout.slots[i].width = SharedBufferWidth;
175 layout.slots[i].height = SharedBufferHeight;
176 }
177
178 return layout;
179}();
180
181void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
182 auto buffer = std::make_shared<android::GraphicBuffer>();
183 buffer->width = SharedBufferWidth;
184 buffer->height = SharedBufferHeight;
185 buffer->stride = SharedBufferBlockLinearStride;
186 buffer->format = SharedBufferBlockLinearFormat;
187 buffer->buffer_id = handle;
188 buffer->offset = slot * SharedBufferSlotSize;
189 ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError);
190}
191
192} // namespace
193
194FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
195 std::shared_ptr<Nvidia::Module> nvdrv)
196 : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {}
197
198FbShareBufferManager::~FbShareBufferManager() = default;
199
200Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
201 std::scoped_lock lk{m_guard};
202
203 // Ensure we have not already created a buffer.
204 R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
205
206 // Allocate memory and space for the shared buffer.
207 Common::ProcessAddress map_address;
208 R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
209 std::addressof(m_buffer_page_group), m_system,
210 SharedBufferSize));
211
212 // Create an nvmap handle for the buffer and assign the memory to it.
213 R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address,
214 SharedBufferSize));
215
216 // Record the display id.
217 m_display_id = display_id;
218
219 // Create a layer for the display.
220 m_layer_id = m_flinger.CreateLayer(m_display_id).value();
221
222 // Set up the buffer.
223 m_buffer_id = m_next_buffer_id++;
224
225 // Get the layer.
226 VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
227 ASSERT(layer != nullptr);
228
229 // Get the producer and set preallocated buffers.
230 auto& producer = layer->GetBufferQueue();
231 MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
232 MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
233
234 // Assign outputs.
235 *out_buffer_id = m_buffer_id;
236 *out_layer_id = m_layer_id;
237
238 // We succeeded.
239 R_SUCCEED();
240}
241
242Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
243 s32* out_nvmap_handle,
244 SharedMemoryPoolLayout* out_pool_layout,
245 u64 buffer_id,
246 u64 applet_resource_user_id) {
247 std::scoped_lock lk{m_guard};
248
249 R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
250 R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
251
252 *out_pool_layout = SharedBufferPoolLayout;
253 *out_buffer_size = SharedBufferSize;
254 *out_nvmap_handle = m_buffer_nvmap_handle;
255
256 R_SUCCEED();
257}
258
259Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
260 // Ensure the layer id is valid.
261 R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
262
263 // Get the layer.
264 VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
265 R_UNLESS(layer != nullptr, VI::ResultNotFound);
266
267 // We succeeded.
268 *out_layer = layer;
269 R_SUCCEED();
270}
271
272Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
273 std::array<s32, 4>& out_slot_indexes,
274 s64* out_target_slot, u64 layer_id) {
275 std::scoped_lock lk{m_guard};
276
277 // Get the layer.
278 VI::Layer* layer;
279 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
280
281 // Get the producer.
282 auto& producer = layer->GetBufferQueue();
283
284 // Get the next buffer from the producer.
285 s32 slot;
286 R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
287 SharedBufferWidth, SharedBufferHeight,
288 SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
289 VI::ResultOperationFailed);
290
291 // Assign remaining outputs.
292 *out_target_slot = slot;
293 out_slot_indexes = {0, 1, -1, -1};
294
295 // We succeeded.
296 R_SUCCEED();
297}
298
299Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
300 Common::Rectangle<s32> crop_region,
301 u32 transform, s32 swap_interval,
302 u64 layer_id, s64 slot) {
303 std::scoped_lock lk{m_guard};
304
305 // Get the layer.
306 VI::Layer* layer;
307 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
308
309 // Get the producer.
310 auto& producer = layer->GetBufferQueue();
311
312 // Request to queue the buffer.
313 std::shared_ptr<android::GraphicBuffer> buffer;
314 R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
315 android::Status::NoError,
316 VI::ResultOperationFailed);
317
318 // Queue the buffer to the producer.
319 android::QueueBufferInput input{};
320 android::QueueBufferOutput output{};
321 input.crop = crop_region;
322 input.fence = fence;
323 input.transform = static_cast<android::NativeWindowTransform>(transform);
324 input.swap_interval = swap_interval;
325 R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
326 android::Status::NoError,
327 VI::ResultOperationFailed);
328
329 // We succeeded.
330 R_SUCCEED();
331}
332
333Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
334 u64 layer_id) {
335 std::scoped_lock lk{m_guard};
336
337 // Get the layer.
338 VI::Layer* layer;
339 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
340
341 // Get the producer.
342 auto& producer = layer->GetBufferQueue();
343
344 // Set the event.
345 *out_event = std::addressof(producer.GetNativeHandle());
346
347 // We succeeded.
348 R_SUCCEED();
349}
350
351} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
new file mode 100644
index 000000000..c809c01b4
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -0,0 +1,65 @@
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/math_util.h"
7#include "core/hle/service/nvnflinger/nvnflinger.h"
8#include "core/hle/service/nvnflinger/ui/fence.h"
9
10namespace Kernel {
11class KPageGroup;
12}
13
14namespace Service::Nvnflinger {
15
16struct SharedMemorySlot {
17 u64 buffer_offset;
18 u64 size;
19 s32 width;
20 s32 height;
21};
22static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size");
23
24struct SharedMemoryPoolLayout {
25 s32 num_slots;
26 std::array<SharedMemorySlot, 0x10> slots;
27};
28static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
29
30class FbShareBufferManager final {
31public:
32 explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
33 std::shared_ptr<Nvidia::Module> nvdrv);
34 ~FbShareBufferManager();
35
36 Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
37 Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
38 SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
39 u64 applet_resource_user_id);
40 Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots,
41 s64* out_target_slot, u64 layer_id);
42 Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
43 u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
44 Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
45
46private:
47 Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
48
49private:
50 u64 m_next_buffer_id = 1;
51 u64 m_display_id = 0;
52 u64 m_buffer_id = 0;
53 u64 m_layer_id = 0;
54 u32 m_buffer_nvmap_handle = 0;
55 SharedMemoryPoolLayout m_pool_layout = {};
56
57 std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
58
59 std::mutex m_guard;
60 Core::System& m_system;
61 Nvnflinger& m_flinger;
62 std::shared_ptr<Nvidia::Module> m_nvdrv;
63};
64
65} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
index 21d7b31f3..5d7cff7d3 100644
--- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
+++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
@@ -19,6 +19,7 @@ class InputParcel;
19#pragma pack(push, 1) 19#pragma pack(push, 1)
20struct QueueBufferInput final { 20struct QueueBufferInput final {
21 explicit QueueBufferInput(InputParcel& parcel); 21 explicit QueueBufferInput(InputParcel& parcel);
22 explicit QueueBufferInput() = default;
22 23
23 void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, 24 void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
24 NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, 25 NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
@@ -34,7 +35,6 @@ struct QueueBufferInput final {
34 *fence_ = fence; 35 *fence_ = fence;
35 } 36 }
36 37
37private:
38 s64 timestamp{}; 38 s64 timestamp{};
39 s32 is_auto_timestamp{}; 39 s32 is_auto_timestamp{};
40 Common::Rectangle<s32> crop{}; 40 Common::Rectangle<s32> crop{};
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 21f31f7a0..a07c621d9 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -17,6 +17,7 @@
17#include "core/hle/service/nvdrv/nvdrv.h" 17#include "core/hle/service/nvdrv/nvdrv.h"
18#include "core/hle/service/nvnflinger/buffer_item_consumer.h" 18#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
19#include "core/hle/service/nvnflinger/buffer_queue_core.h" 19#include "core/hle/service/nvnflinger/buffer_queue_core.h"
20#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
20#include "core/hle/service/nvnflinger/hos_binder_driver_server.h" 21#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
21#include "core/hle/service/nvnflinger/nvnflinger.h" 22#include "core/hle/service/nvnflinger/nvnflinger.h"
22#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" 23#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
@@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const {
331 return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); 332 return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
332} 333}
333 334
335FbShareBufferManager& Nvnflinger::GetSystemBufferManager() {
336 const auto lock_guard = Lock();
337
338 if (!system_buffer_manager) {
339 system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv);
340 }
341
342 return *system_buffer_manager;
343}
344
334} // namespace Service::Nvnflinger 345} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index f478c2bc6..14c783582 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -45,6 +45,9 @@ class BufferQueueProducer;
45 45
46namespace Service::Nvnflinger { 46namespace Service::Nvnflinger {
47 47
48class FbShareBufferManager;
49class HosBinderDriverServer;
50
48class Nvnflinger final { 51class Nvnflinger final {
49public: 52public:
50 explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); 53 explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
@@ -90,12 +93,16 @@ public:
90 93
91 [[nodiscard]] s64 GetNextTicks() const; 94 [[nodiscard]] s64 GetNextTicks() const;
92 95
96 FbShareBufferManager& GetSystemBufferManager();
97
93private: 98private:
94 struct Layer { 99 struct Layer {
95 std::unique_ptr<android::BufferQueueCore> core; 100 std::unique_ptr<android::BufferQueueCore> core;
96 std::unique_ptr<android::BufferQueueProducer> producer; 101 std::unique_ptr<android::BufferQueueProducer> producer;
97 }; 102 };
98 103
104 friend class FbShareBufferManager;
105
99private: 106private:
100 [[nodiscard]] std::unique_lock<std::mutex> Lock() const { 107 [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
101 return std::unique_lock{*guard}; 108 return std::unique_lock{*guard};
@@ -140,6 +147,8 @@ private:
140 std::shared_ptr<Core::Timing::EventType> multi_composition_event; 147 std::shared_ptr<Core::Timing::EventType> multi_composition_event;
141 std::shared_ptr<Core::Timing::EventType> single_composition_event; 148 std::shared_ptr<Core::Timing::EventType> single_composition_event;
142 149
150 std::unique_ptr<FbShareBufferManager> system_buffer_manager;
151
143 std::shared_ptr<std::mutex> guard; 152 std::shared_ptr<std::mutex> guard;
144 153
145 Core::System& system; 154 Core::System& system;
diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h
index 536e8156d..177aed758 100644
--- a/src/core/hle/service/nvnflinger/ui/fence.h
+++ b/src/core/hle/service/nvnflinger/ui/fence.h
@@ -20,6 +20,9 @@ public:
20 static constexpr Fence NoFence() { 20 static constexpr Fence NoFence() {
21 Fence fence; 21 Fence fence;
22 fence.fences[0].id = -1; 22 fence.fences[0].id = -1;
23 fence.fences[1].id = -1;
24 fence.fences[2].id = -1;
25 fence.fences[3].id = -1;
23 return fence; 26 return fence;
24 } 27 }
25 28
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
index 75d1705a8..3eac5cedd 100644
--- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
@@ -12,8 +12,7 @@
12 12
13namespace Service::android { 13namespace Service::android {
14 14
15class GraphicBuffer final { 15struct GraphicBuffer final {
16public:
17 constexpr GraphicBuffer() = default; 16 constexpr GraphicBuffer() = default;
18 17
19 constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) 18 constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
@@ -77,7 +76,6 @@ public:
77 return false; 76 return false;
78 } 77 }
79 78
80private:
81 u32 magic{}; 79 u32 magic{};
82 s32 width{}; 80 s32 width{};
83 s32 height{}; 81 s32 height{};
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 5db1703d1..938330dd0 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -33,7 +33,7 @@ public:
33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"}, 33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
34 {1002, nullptr, "ConfirmLaunchApplicationPermission"}, 34 {1002, nullptr, "ConfirmLaunchApplicationPermission"},
35 {1003, nullptr, "ConfirmResumeApplicationPermission"}, 35 {1003, nullptr, "ConfirmResumeApplicationPermission"},
36 {1004, nullptr, "ConfirmSnsPostPermission"}, 36 {1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
37 {1005, nullptr, "ConfirmSystemSettingsPermission"}, 37 {1005, nullptr, "ConfirmSystemSettingsPermission"},
38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"}, 38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, 39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@@ -236,6 +236,13 @@ private:
236 states.free_communication = true; 236 states.free_communication = true;
237 } 237 }
238 238
239 void ConfirmSnsPostPermission(HLERequestContext& ctx) {
240 LOG_WARNING(Service_PCTL, "(STUBBED) called");
241
242 IPC::ResponseBuilder rb{ctx, 2};
243 rb.Push(Error::ResultNoFreeCommunication);
244 }
245
239 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { 246 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
240 const bool is_temporary_unlocked = false; 247 const bool is_temporary_unlocked = false;
241 248
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 2eb978379..b1bfb9898 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -20,9 +20,12 @@
20#include "core/hle/kernel/k_readable_event.h" 20#include "core/hle/kernel/k_readable_event.h"
21#include "core/hle/kernel/k_thread.h" 21#include "core/hle/kernel/k_thread.h"
22#include "core/hle/service/ipc_helpers.h" 22#include "core/hle/service/ipc_helpers.h"
23#include "core/hle/service/nvdrv/devices/nvmap.h"
23#include "core/hle/service/nvdrv/nvdata.h" 24#include "core/hle/service/nvdrv/nvdata.h"
25#include "core/hle/service/nvdrv/nvdrv.h"
24#include "core/hle/service/nvnflinger/binder.h" 26#include "core/hle/service/nvnflinger/binder.h"
25#include "core/hle/service/nvnflinger/buffer_queue_producer.h" 27#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
28#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
26#include "core/hle/service/nvnflinger/hos_binder_driver_server.h" 29#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
27#include "core/hle/service/nvnflinger/nvnflinger.h" 30#include "core/hle/service/nvnflinger/nvnflinger.h"
28#include "core/hle/service/nvnflinger/parcel.h" 31#include "core/hle/service/nvnflinger/parcel.h"
@@ -131,8 +134,9 @@ private:
131 134
132class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { 135class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
133public: 136public:
134 explicit ISystemDisplayService(Core::System& system_) 137 explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
135 : ServiceFramework{system_, "ISystemDisplayService"} { 138 : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} {
139 // clang-format off
136 static const FunctionInfo functions[] = { 140 static const FunctionInfo functions[] = {
137 {1200, nullptr, "GetZOrderCountMin"}, 141 {1200, nullptr, "GetZOrderCountMin"},
138 {1202, nullptr, "GetZOrderCountMax"}, 142 {1202, nullptr, "GetZOrderCountMax"},
@@ -170,22 +174,126 @@ public:
170 {3217, nullptr, "SetDisplayCmuLuma"}, 174 {3217, nullptr, "SetDisplayCmuLuma"},
171 {3218, nullptr, "SetDisplayCrcMode"}, 175 {3218, nullptr, "SetDisplayCrcMode"},
172 {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"}, 176 {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
173 {8225, nullptr, "GetSharedBufferMemoryHandleId"}, 177 {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"},
174 {8250, nullptr, "OpenSharedLayer"}, 178 {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"},
175 {8251, nullptr, "CloseSharedLayer"}, 179 {8251, nullptr, "CloseSharedLayer"},
176 {8252, nullptr, "ConnectSharedLayer"}, 180 {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"},
177 {8253, nullptr, "DisconnectSharedLayer"}, 181 {8253, nullptr, "DisconnectSharedLayer"},
178 {8254, nullptr, "AcquireSharedFrameBuffer"}, 182 {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"},
179 {8255, nullptr, "PresentSharedFrameBuffer"}, 183 {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"},
180 {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"}, 184 {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"},
181 {8257, nullptr, "FillSharedFrameBufferColor"}, 185 {8257, nullptr, "FillSharedFrameBufferColor"},
182 {8258, nullptr, "CancelSharedFrameBuffer"}, 186 {8258, nullptr, "CancelSharedFrameBuffer"},
183 {9000, nullptr, "GetDp2hdmiController"}, 187 {9000, nullptr, "GetDp2hdmiController"},
184 }; 188 };
189 // clang-format on
185 RegisterHandlers(functions); 190 RegisterHandlers(functions);
186 } 191 }
187 192
188private: 193private:
194 void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) {
195 IPC::RequestParser rp{ctx};
196 const u64 buffer_id = rp.PopRaw<u64>();
197
198 LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id);
199
200 struct OutputParameters {
201 s32 nvmap_handle;
202 u64 size;
203 };
204
205 OutputParameters out{};
206 Nvnflinger::SharedMemoryPoolLayout layout{};
207 const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId(
208 &out.size, &out.nvmap_handle, &layout, buffer_id, 0);
209
210 ctx.WriteBuffer(&layout, sizeof(layout));
211
212 IPC::ResponseBuilder rb{ctx, 6};
213 rb.Push(result);
214 rb.PushRaw(out);
215 }
216
217 void OpenSharedLayer(HLERequestContext& ctx) {
218 IPC::RequestParser rp{ctx};
219 const u64 layer_id = rp.PopRaw<u64>();
220
221 LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
222
223 IPC::ResponseBuilder rb{ctx, 2};
224 rb.Push(ResultSuccess);
225 }
226
227 void ConnectSharedLayer(HLERequestContext& ctx) {
228 IPC::RequestParser rp{ctx};
229 const u64 layer_id = rp.PopRaw<u64>();
230
231 LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
232
233 IPC::ResponseBuilder rb{ctx, 2};
234 rb.Push(ResultSuccess);
235 }
236
237 void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) {
238 LOG_DEBUG(Service_VI, "called");
239
240 IPC::RequestParser rp{ctx};
241 const u64 layer_id = rp.PopRaw<u64>();
242
243 Kernel::KReadableEvent* event{};
244 const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent(
245 &event, layer_id);
246
247 IPC::ResponseBuilder rb{ctx, 2, 1};
248 rb.Push(result);
249 rb.PushCopyObjects(event);
250 }
251
252 void AcquireSharedFrameBuffer(HLERequestContext& ctx) {
253 LOG_DEBUG(Service_VI, "called");
254
255 IPC::RequestParser rp{ctx};
256 const u64 layer_id = rp.PopRaw<u64>();
257
258 struct OutputParameters {
259 android::Fence fence;
260 std::array<s32, 4> slots;
261 s64 target_slot;
262 };
263 static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size");
264
265 OutputParameters out{};
266 const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer(
267 &out.fence, out.slots, &out.target_slot, layer_id);
268
269 IPC::ResponseBuilder rb{ctx, 18};
270 rb.Push(result);
271 rb.PushRaw(out);
272 }
273
274 void PresentSharedFrameBuffer(HLERequestContext& ctx) {
275 LOG_DEBUG(Service_VI, "called");
276
277 struct InputParameters {
278 android::Fence fence;
279 Common::Rectangle<s32> crop_region;
280 u32 window_transform;
281 s32 swap_interval;
282 u64 layer_id;
283 s64 surface_id;
284 };
285 static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size");
286
287 IPC::RequestParser rp{ctx};
288 auto input = rp.PopRaw<InputParameters>();
289
290 const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer(
291 input.fence, input.crop_region, input.window_transform, input.swap_interval,
292 input.layer_id, input.surface_id);
293 IPC::ResponseBuilder rb{ctx, 2};
294 rb.Push(result);
295 }
296
189 void SetLayerZ(HLERequestContext& ctx) { 297 void SetLayerZ(HLERequestContext& ctx) {
190 IPC::RequestParser rp{ctx}; 298 IPC::RequestParser rp{ctx};
191 const u64 layer_id = rp.Pop<u64>(); 299 const u64 layer_id = rp.Pop<u64>();
@@ -228,6 +336,9 @@ private:
228 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. 336 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
229 rb.Push<u32>(0); 337 rb.Push<u32>(0);
230 } 338 }
339
340private:
341 Nvnflinger::Nvnflinger& nvnflinger;
231}; 342};
232 343
233class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { 344class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -453,7 +564,7 @@ private:
453 564
454 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 565 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
455 rb.Push(ResultSuccess); 566 rb.Push(ResultSuccess);
456 rb.PushIpcInterface<ISystemDisplayService>(system); 567 rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
457 } 568 }
458 569
459 void GetManagerDisplayService(HLERequestContext& ctx) { 570 void GetManagerDisplayService(HLERequestContext& ctx) {
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 5a42dea48..5c36b71e5 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
118 return {ResultStatus::ErrorMissingNPDM, {}}; 118 return {ResultStatus::ErrorMissingNPDM, {}};
119 } 119 }
120 120
121 const ResultStatus result2 = metadata.Load(npdm); 121 const ResultStatus result2 = metadata.Reload(npdm);
122 if (result2 != ResultStatus::Success) { 122 if (result2 != ResultStatus::Success) {
123 return {result2, {}}; 123 return {result2, {}};
124 } 124 }
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
index 44d24822a..947fa6cb3 100644
--- a/src/core/tools/renderdoc.cpp
+++ b/src/core/tools/renderdoc.cpp
@@ -7,7 +7,7 @@
7#include "common/dynamic_library.h" 7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h" 8#include "core/tools/renderdoc.h"
9 9
10#ifdef WIN32 10#ifdef _WIN32
11#include <windows.h> 11#include <windows.h>
12#else 12#else
13#include <dlfcn.h> 13#include <dlfcn.h>
diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h
index dfbc45a28..3a40e3fd3 100644
--- a/src/input_common/drivers/virtual_gamepad.h
+++ b/src/input_common/drivers/virtual_gamepad.h
@@ -67,7 +67,7 @@ public:
67 * @param player_index the player number that will take this action 67 * @param player_index the player number that will take this action
68 * @param delta_timestamp time passed since last reading 68 * @param delta_timestamp time passed since last reading
69 * @param gyro_x,gyro_y,gyro_z the gyro sensor readings 69 * @param gyro_x,gyro_y,gyro_z the gyro sensor readings
70 * @param accel_x,accel_y,accel_z the acelerometer reading 70 * @param accel_x,accel_y,accel_z the accelerometer reading
71 */ 71 */
72 void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y, 72 void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
73 float gyro_z, float accel_x, float accel_y, float accel_z); 73 float gyro_z, float accel_x, float accel_y, float accel_z);
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
index 90fcd17f6..b94567f82 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.h
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -55,7 +55,7 @@ public:
55 55
56 /** 56 /**
57 * Configures the motion sensor with the specified parameters 57 * Configures the motion sensor with the specified parameters
58 * @param gsen gyroscope sensor sensitvity in degrees per second 58 * @param gsen gyroscope sensor sensitivity in degrees per second
59 * @param gfrec gyroscope sensor frequency in hertz 59 * @param gfrec gyroscope sensor frequency in hertz
60 * @param asen accelerometer sensitivity in G force 60 * @param asen accelerometer sensitivity in G force
61 * @param afrec accelerometer frequency in hertz 61 * @param afrec accelerometer frequency in hertz
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
index d508ee567..4e8ba4ae6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
@@ -55,7 +55,7 @@ void CompositeInsert(EmitContext& ctx, IR::Inst& inst, Register composite, Objec
55 "MOV.{} {}.{},{};", 55 "MOV.{} {}.{},{};",
56 type, ret, composite, type, ret, swizzle, object); 56 type, ret, composite, type, ret, swizzle, object);
57 } else { 57 } else {
58 // The return value is alised so we can just insert the object, it doesn't matter if it's 58 // The return value is aliased so we can just insert the object, it doesn't matter if it's
59 // aliased 59 // aliased
60 ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object); 60 ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object);
61 } 61 }
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/dma_pusher.h b/src/video_core/dma_pusher.h
index c9fab2d90..e46a8fa5c 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -161,7 +161,7 @@ private:
161 u32 method_count; ///< Current method count 161 u32 method_count; ///< Current method count
162 u32 length_pending; ///< Large NI command length pending 162 u32 length_pending; ///< Large NI command length pending
163 GPUVAddr dma_get; ///< Currently read segment 163 GPUVAddr dma_get; ///< Currently read segment
164 u64 dma_word_offset; ///< Current word ofset from address 164 u64 dma_word_offset; ///< Current word offset from address
165 bool non_incrementing; ///< Current command's NI flag 165 bool non_incrementing; ///< Current command's NI flag
166 bool is_last_call; 166 bool is_last_call;
167 }; 167 };
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..8bb429578 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -19,6 +19,7 @@ set(SHADER_FILES
19 block_linear_unswizzle_2d.comp 19 block_linear_unswizzle_2d.comp
20 block_linear_unswizzle_3d.comp 20 block_linear_unswizzle_3d.comp
21 convert_abgr8_to_d24s8.frag 21 convert_abgr8_to_d24s8.frag
22 convert_d32f_to_abgr8.frag
22 convert_d24s8_to_abgr8.frag 23 convert_d24s8_to_abgr8.frag
23 convert_depth_to_float.frag 24 convert_depth_to_float.frag
24 convert_float_to_depth.frag 25 convert_float_to_depth.frag
@@ -41,6 +42,9 @@ set(SHADER_FILES
41 pitch_unswizzle.comp 42 pitch_unswizzle.comp
42 present_bicubic.frag 43 present_bicubic.frag
43 present_gaussian.frag 44 present_gaussian.frag
45 queries_prefix_scan_sum.comp
46 queries_prefix_scan_sum_nosubgroups.comp
47 resolve_conditional_render.comp
44 smaa_edge_detection.vert 48 smaa_edge_detection.vert
45 smaa_edge_detection.frag 49 smaa_edge_detection.frag
46 smaa_blending_weight_calculation.vert 50 smaa_blending_weight_calculation.vert
@@ -70,6 +74,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
70endif() 74endif()
71 75
72set(GLSL_FLAGS "") 76set(GLSL_FLAGS "")
77set(SPIR_V_VERSION "spirv1.3")
73set(QUIET_FLAG "--quiet") 78set(QUIET_FLAG "--quiet")
74 79
75set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) 80set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
@@ -123,7 +128,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
123 OUTPUT 128 OUTPUT
124 ${SPIRV_HEADER_FILE} 129 ${SPIRV_HEADER_FILE}
125 COMMAND 130 COMMAND
126 ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} 131 ${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 132 MAIN_DEPENDENCY
128 ${SOURCE_FILE} 133 ${SOURCE_FILE}
129 ) 134 )
diff --git a/src/video_core/host_shaders/convert_d32f_to_abgr8.frag b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
new file mode 100644
index 000000000..04cfef8b5
--- /dev/null
+++ b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#version 450
5
6layout(binding = 0) uniform sampler2D depth_tex;
7
8layout(location = 0) out vec4 color;
9
10void main() {
11 ivec2 coord = ivec2(gl_FragCoord.xy);
12 float depth = textureLod(depth_tex, coord, 0).r;
13 color = vec4(depth, depth, depth, 1.0);
14}
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..44769ea97
--- /dev/null
+++ b/src/video_core/query_cache/bank_base.h
@@ -0,0 +1,105 @@
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 bank_indices.push_back(new_index);
86 return new_index;
87 }
88 size_t new_index = bank_pool.size();
89 builder(bank_pool, new_index);
90 bank_indices.push_back(new_index);
91 return new_index;
92 }
93
94 // Get a reference to a bank using its index
95 BankType& GetBank(size_t index) {
96 return bank_pool[index];
97 }
98
99 // Get the total number of banks in the pool
100 size_t BankCount() const {
101 return bank_pool.size();
102 }
103};
104
105} // 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_base.h b/src/video_core/renderer_base.h
index 3e12a8813..78ea5208b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -89,9 +89,6 @@ public:
89 void RequestScreenshot(void* data, std::function<void(bool)> callback, 89 void RequestScreenshot(void* data, std::function<void(bool)> callback,
90 const Layout::FramebufferLayout& layout); 90 const Layout::FramebufferLayout& layout);
91 91
92 /// This is called to notify the rendering backend of a surface change
93 virtual void NotifySurfaceChanged() {}
94
95protected: 92protected:
96 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. 93 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
97 std::unique_ptr<Core::Frontend::GraphicsContext> context; 94 std::unique_ptr<Core::Frontend::GraphicsContext> context;
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_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index c7dc7e0a1..5ea9e2378 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT 116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT 117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM 118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
119 {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
119 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT 120 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
120 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT 121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM 122 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 1032c9d12..f01d2394e 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -9,6 +9,7 @@
9#include "video_core/host_shaders/blit_color_float_frag_spv.h" 9#include "video_core/host_shaders/blit_color_float_frag_spv.h"
10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" 10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
11#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" 11#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
12#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
12#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" 13#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
13#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" 14#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
14#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" 15#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
@@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
433 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), 434 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
434 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), 435 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
435 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), 436 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
437 convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
436 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), 438 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
437 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), 439 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
438 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), 440 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
@@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
557 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); 559 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
558} 560}
559 561
562void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
563 ImageView& src_image_view) {
564 ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
565 convert_d32f_to_abgr8_frag);
566 ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
567}
568
560void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, 569void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
561 ImageView& src_image_view) { 570 ImageView& src_image_view) {
562 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), 571 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
@@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
609 const VkPipelineLayout layout = *clear_color_pipeline_layout; 618 const VkPipelineLayout layout = *clear_color_pipeline_layout;
610 scheduler.RequestRenderpass(dst_framebuffer); 619 scheduler.RequestRenderpass(dst_framebuffer);
611 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { 620 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
621 constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
622 cmdbuf.SetBlendConstants(blend_constants.data());
612 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); 623 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
613 BindBlitState(cmdbuf, dst_region); 624 BindBlitState(cmdbuf, dst_region);
614 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); 625 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
865 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, 876 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
866 .pNext = nullptr, 877 .pNext = nullptr,
867 .flags = 0, 878 .flags = 0,
868 .depthTestEnable = VK_FALSE, 879 .depthTestEnable = key.depth_clear,
869 .depthWriteEnable = key.depth_clear, 880 .depthWriteEnable = key.depth_clear,
870 .depthCompareOp = VK_COMPARE_OP_ALWAYS, 881 .depthCompareOp = VK_COMPARE_OP_ALWAYS,
871 .depthBoundsTestEnable = VK_FALSE, 882 .depthBoundsTestEnable = VK_FALSE,
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index dcfe217aa..a032c71fb 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -67,6 +67,8 @@ public:
67 67
68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); 68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
69 69
70 void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
71
70 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 72 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
71 73
72 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 74 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@@ -128,6 +130,7 @@ private:
128 vk::ShaderModule convert_depth_to_float_frag; 130 vk::ShaderModule convert_depth_to_float_frag;
129 vk::ShaderModule convert_float_to_depth_frag; 131 vk::ShaderModule convert_float_to_depth_frag;
130 vk::ShaderModule convert_abgr8_to_d24s8_frag; 132 vk::ShaderModule convert_abgr8_to_d24s8_frag;
133 vk::ShaderModule convert_d32f_to_abgr8_frag;
131 vk::ShaderModule convert_d24s8_to_abgr8_frag; 134 vk::ShaderModule convert_d24s8_to_abgr8_frag;
132 vk::ShaderModule convert_s8d24_to_abgr8_frag; 135 vk::ShaderModule convert_s8d24_to_abgr8_frag;
133 vk::Sampler linear_sampler; 136 vk::Sampler linear_sampler;
@@ -146,6 +149,7 @@ private:
146 vk::Pipeline convert_d16_to_r16_pipeline; 149 vk::Pipeline convert_d16_to_r16_pipeline;
147 vk::Pipeline convert_r16_to_d16_pipeline; 150 vk::Pipeline convert_r16_to_d16_pipeline;
148 vk::Pipeline convert_abgr8_to_d24s8_pipeline; 151 vk::Pipeline convert_abgr8_to_d24s8_pipeline;
152 vk::Pipeline convert_d32f_to_abgr8_pipeline;
149 vk::Pipeline convert_d24s8_to_abgr8_pipeline; 153 vk::Pipeline convert_d24s8_to_abgr8_pipeline;
150 vk::Pipeline convert_s8d24_to_abgr8_pipeline; 154 vk::Pipeline convert_s8d24_to_abgr8_pipeline;
151}; 155};
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 35bf80ea3..a08f2f67f 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
@@ -214,8 +214,9 @@ struct FormatTuple {
214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT 214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
215 215
216 // Depth formats 216 // Depth formats
217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT 217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM 218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
219 {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
219 220
220 // Stencil formats 221 // Stencil formats
221 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT 222 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 590bc1c64..14e257cf7 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -56,10 +56,6 @@ public:
56 return device.GetDriverName(); 56 return device.GetDriverName();
57 } 57 }
58 58
59 void NotifySurfaceChanged() override {
60 present_manager.NotifySurfaceChanged();
61 }
62
63private: 59private:
64 void Report() const; 60 void Report() const;
65 61
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 31928bb94..52fc142d1 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { 96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
97 switch (framebuffer.pixel_format) { 97 switch (framebuffer.pixel_format) {
98 case Service::android::PixelFormat::Rgba8888: 98 case Service::android::PixelFormat::Rgba8888:
99 case Service::android::PixelFormat::Rgbx8888:
99 return VK_FORMAT_A8B8G8R8_UNORM_PACK32; 100 return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
100 case Service::android::PixelFormat::Rgb565: 101 case Service::android::PixelFormat::Rgb565:
101 return VK_FORMAT_R5G6B5_UNORM_PACK16; 102 return VK_FORMAT_R5G6B5_UNORM_PACK16;
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_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index d681bd22a..2ef36583b 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), 103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
104 swapchain.GetImageViewFormat())}, 104 swapchain.GetImageViewFormat())},
105 use_present_thread{Settings::values.async_presentation.GetValue()}, 105 use_present_thread{Settings::values.async_presentation.GetValue()},
106 image_count{swapchain.GetImageCount()}, last_render_surface{ 106 image_count{swapchain.GetImageCount()} {
107 render_window_.GetWindowInfo().render_surface} {
108 107
109 auto& dld = device.GetLogical(); 108 auto& dld = device.GetLogical();
110 cmdpool = dld.CreateCommandPool({ 109 cmdpool = dld.CreateCommandPool({
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
289 } 288 }
290} 289}
291 290
292void PresentManager::NotifySurfaceChanged() { 291void PresentManager::RecreateSwapchain(Frame* frame) {
293#ifdef ANDROID 292 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
294 std::scoped_lock lock{recreate_surface_mutex}; 293 image_count = swapchain.GetImageCount();
295 recreate_surface_cv.notify_one();
296#endif
297} 294}
298 295
299void PresentManager::CopyToSwapchain(Frame* frame) { 296void PresentManager::CopyToSwapchain(Frame* frame) {
300 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); 297 bool requires_recreation = false;
301 298
302 const auto recreate_swapchain = [&] { 299 while (true) {
303 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb); 300 try {
304 image_count = swapchain.GetImageCount(); 301 // Recreate surface and swapchain if needed.
305 }; 302 if (requires_recreation) {
306 303 surface = CreateSurface(instance, render_window.GetWindowInfo());
307#ifdef ANDROID 304 RecreateSwapchain(frame);
308 std::unique_lock lock{recreate_surface_mutex}; 305 }
309 306
310 const auto needs_recreation = [&] { 307 // Draw to swapchain.
311 if (last_render_surface != render_window.GetWindowInfo().render_surface) { 308 return CopyToSwapchainImpl(frame);
312 return true; 309 } catch (const vk::Exception& except) {
313 } 310 if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
314 if (swapchain.NeedsRecreation(frame->is_srgb)) { 311 throw;
315 return true; 312 }
313
314 requires_recreation = true;
316 } 315 }
317 return false;
318 };
319
320 recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
321 [&]() { return !needs_recreation(); });
322
323 // If the frontend recreated the surface, recreate the renderer surface and swapchain.
324 if (last_render_surface != render_window.GetWindowInfo().render_surface) {
325 last_render_surface = render_window.GetWindowInfo().render_surface;
326 surface = CreateSurface(instance, render_window.GetWindowInfo());
327 recreate_swapchain();
328 } 316 }
329#endif 317}
318
319void PresentManager::CopyToSwapchainImpl(Frame* frame) {
320 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
330 321
331 // If the size or colorspace of the incoming frames has changed, recreate the swapchain 322 // If the size or colorspace of the incoming frames has changed, recreate the swapchain
332 // to account for that. 323 // to account for that.
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
334 const bool size_changed = 325 const bool size_changed =
335 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; 326 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
336 if (srgb_changed || size_changed) { 327 if (srgb_changed || size_changed) {
337 recreate_swapchain(); 328 RecreateSwapchain(frame);
338 } 329 }
339 330
340 while (swapchain.AcquireNextImage()) { 331 while (swapchain.AcquireNextImage()) {
341 recreate_swapchain(); 332 RecreateSwapchain(frame);
342 } 333 }
343 334
344 const vk::CommandBuffer cmdbuf{frame->cmdbuf}; 335 const vk::CommandBuffer cmdbuf{frame->cmdbuf};
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
488 swapchain.Present(render_semaphore); 479 swapchain.Present(render_semaphore);
489} 480}
490 481
491} // namespace Vulkan \ No newline at end of file 482} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 83e859416..a3d825fe6 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -54,14 +54,15 @@ public:
54 /// Waits for the present thread to finish presenting all queued frames. 54 /// Waits for the present thread to finish presenting all queued frames.
55 void WaitPresent(); 55 void WaitPresent();
56 56
57 /// This is called to notify the rendering backend of a surface change
58 void NotifySurfaceChanged();
59
60private: 57private:
61 void PresentThread(std::stop_token token); 58 void PresentThread(std::stop_token token);
62 59
63 void CopyToSwapchain(Frame* frame); 60 void CopyToSwapchain(Frame* frame);
64 61
62 void CopyToSwapchainImpl(Frame* frame);
63
64 void RecreateSwapchain(Frame* frame);
65
65private: 66private:
66 const vk::Instance& instance; 67 const vk::Instance& instance;
67 Core::Frontend::EmuWindow& render_window; 68 Core::Frontend::EmuWindow& render_window;
@@ -76,16 +77,13 @@ private:
76 std::queue<Frame*> free_queue; 77 std::queue<Frame*> free_queue;
77 std::condition_variable_any frame_cv; 78 std::condition_variable_any frame_cv;
78 std::condition_variable free_cv; 79 std::condition_variable free_cv;
79 std::condition_variable recreate_surface_cv;
80 std::mutex swapchain_mutex; 80 std::mutex swapchain_mutex;
81 std::mutex recreate_surface_mutex;
82 std::mutex queue_mutex; 81 std::mutex queue_mutex;
83 std::mutex free_mutex; 82 std::mutex free_mutex;
84 std::jthread present_thread; 83 std::jthread present_thread;
85 bool blit_supported; 84 bool blit_supported;
86 bool use_present_thread; 85 bool use_present_thread;
87 std::size_t image_count{}; 86 std::size_t image_count{};
88 void* last_render_surface{};
89}; 87};
90 88
91} // namespace Vulkan 89} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 29e0b797b..2edaafa7e 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1,139 +1,1555 @@
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 rasterizer->SyncOperation(std::move(func));
510 }
511
512 template <bool is_resolve>
513 size_t ObtainBuffer(size_t num_needed) {
514 const size_t log_2 = std::max<size_t>(11U, Common::Log2Ceil64(num_needed));
515 if constexpr (is_resolve) {
516 if (resolve_table[log_2] != 0) {
517 return resolve_table[log_2] - 1;
518 }
519 } else {
520 if (intermediary_table[log_2] != 0) {
521 return intermediary_table[log_2] - 1;
522 }
523 }
524 const VkBufferCreateInfo buffer_ci = {
525 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
526 .pNext = nullptr,
527 .flags = 0,
528 .size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2),
529 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
530 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
531 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
532 .queueFamilyIndexCount = 0,
533 .pQueueFamilyIndices = nullptr,
534 };
535 buffers.emplace_back(memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal));
536 if constexpr (is_resolve) {
537 resolve_table[log_2] = buffers.size();
538 } else {
539 intermediary_table[log_2] = buffers.size();
540 }
541 return buffers.size() - 1;
542 }
543
544 QueryCacheRuntime& runtime;
545 VideoCore::RasterizerInterface* rasterizer;
546 const Device& device;
547 Scheduler& scheduler;
548 const MemoryAllocator& memory_allocator;
549 VideoCommon::BankPool<SamplesQueryBank> bank_pool;
550 std::deque<vk::Buffer> buffers;
551 std::array<size_t, 32> resolve_table{};
552 std::array<size_t, 32> intermediary_table{};
553 vk::Buffer accumulation_buffer;
554 std::deque<std::vector<HostSyncValues>> sync_values_stash;
555 std::vector<size_t> resolve_buffers;
556
557 // syncing queue
558 std::vector<size_t> pending_sync;
559
560 // flush levels
561 std::vector<size_t> pending_flush_queries;
562 std::deque<std::vector<size_t>> pending_flush_sets;
563
564 // State Machine
565 size_t current_bank_slot;
566 size_t current_bank_id;
567 SamplesQueryBank* current_bank;
568 VkQueryPool current_query_pool;
569 size_t current_query_id;
570 size_t num_slots_used{};
571 size_t first_accumulation_checkpoint{};
572 size_t last_accumulation_checkpoint{};
573 bool accumulation_since_last_sync{};
574 VideoCommon::HostQueryBase* current_query;
575 bool has_started{};
576 std::mutex flush_guard;
577
578 std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
579};
580
581// Transform feedback queries
582class TFBQueryBank : public VideoCommon::BankBase {
583public:
584 static constexpr size_t BANK_SIZE = 1024;
585 static constexpr size_t QUERY_SIZE = 4;
586 explicit TFBQueryBank(Scheduler& scheduler_, const MemoryAllocator& memory_allocator,
587 size_t index_)
588 : BankBase(BANK_SIZE), scheduler{scheduler_}, index{index_} {
589 const VkBufferCreateInfo buffer_ci = {
590 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
591 .pNext = nullptr,
592 .flags = 0,
593 .size = QUERY_SIZE * BANK_SIZE,
594 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
595 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
596 .queueFamilyIndexCount = 0,
597 .pQueueFamilyIndices = nullptr,
598 };
599 buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
600 }
601
602 ~TFBQueryBank() = default;
603
604 void Reset() override {
605 ASSERT(references == 0);
606 VideoCommon::BankBase::Reset();
607 }
608
609 void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
610 scheduler.RequestOutsideRenderPassOperationContext();
611 scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
612 size](vk::CommandBuffer cmdbuf) {
613 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
614 .srcOffset = start * QUERY_SIZE,
615 .dstOffset = extra_offset,
616 .size = size * QUERY_SIZE,
617 }};
618 cmdbuf.CopyBuffer(*buffer, dst_buffer, copy);
619 });
620 }
621
622 size_t GetIndex() const {
623 return index;
624 }
625
626 VkBuffer GetBuffer() const {
627 return *buffer;
628 }
629
630private:
631 Scheduler& scheduler;
632 const size_t index;
633 vk::Buffer buffer;
634};
635
636class PrimitivesSucceededStreamer;
637
638class TFBCounterStreamer : public BaseStreamer {
639public:
640 explicit TFBCounterStreamer(size_t id_, QueryCacheRuntime& runtime_, const Device& device_,
641 Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
642 StagingBufferPool& staging_pool_)
643 : BaseStreamer(id_), runtime{runtime_}, device{device_}, scheduler{scheduler_},
644 memory_allocator{memory_allocator_}, staging_pool{staging_pool_} {
645 buffers_count = 0;
646 current_bank = nullptr;
647 counter_buffers.fill(VK_NULL_HANDLE);
648 offsets.fill(0);
649 last_queries.fill(0);
650 last_queries_stride.fill(1);
651 const VkBufferCreateInfo buffer_ci = {
652 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
653 .pNext = nullptr,
654 .flags = 0,
655 .size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS,
656 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
657 VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT,
658 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
659 .queueFamilyIndexCount = 0,
660 .pQueueFamilyIndices = nullptr,
661 };
662
663 counters_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
664 for (auto& c : counter_buffers) {
665 c = *counters_buffer;
666 }
667 size_t base_offset = 0;
668 for (auto& o : offsets) {
669 o = base_offset;
670 base_offset += TFBQueryBank::QUERY_SIZE;
671 }
672 }
673
674 ~TFBCounterStreamer() = default;
675
676 void StartCounter() override {
677 FlushBeginTFB();
678 has_started = true;
679 }
680
681 void PauseCounter() override {
682 CloseCounter();
683 }
684
685 void ResetCounter() override {
686 CloseCounter();
687 }
688
689 void CloseCounter() override {
690 if (has_flushed_end_pending) {
691 FlushEndTFB();
692 }
693 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
694 if (maxwell3d.regs.transform_feedback_enabled == 0) {
695 streams_mask = 0;
696 has_started = false;
697 }
698 });
699 }
700
701 bool HasPendingSync() const override {
702 return !pending_sync.empty();
703 }
704
705 void SyncWrites() override {
706 CloseCounter();
707 std::unordered_map<size_t, std::vector<HostSyncValues>> sync_values_stash;
708 for (auto q : pending_sync) {
709 auto* query = GetQuery(q);
710 if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
711 continue;
712 }
713 if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
714 continue;
715 }
716 query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
717 sync_values_stash.try_emplace(query->start_bank_id);
718 sync_values_stash[query->start_bank_id].emplace_back(HostSyncValues{
719 .address = query->guest_address,
720 .size = TFBQueryBank::QUERY_SIZE,
721 .offset = query->start_slot * TFBQueryBank::QUERY_SIZE,
722 });
723 }
724 for (auto& p : sync_values_stash) {
725 auto& bank = bank_pool.GetBank(p.first);
726 runtime.template SyncValues<HostSyncValues>(p.second, bank.GetBuffer());
727 }
728 pending_sync.clear();
729 }
730
731 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
732 std::optional<u32> subreport_) override {
733 auto index = BuildQuery();
734 auto* new_query = GetQuery(index);
735 new_query->guest_address = address;
736 new_query->value = 0;
737 new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
738 if (has_timestamp) {
739 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
740 }
741 if (!subreport_) {
742 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
743 return index;
744 }
745 const size_t subreport = static_cast<size_t>(*subreport_);
746 last_queries[subreport] = address;
747 if ((streams_mask & (1ULL << subreport)) == 0) {
748 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
749 return index;
750 }
751 CloseCounter();
752 auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport);
753 new_query->start_bank_id = static_cast<u32>(bank_slot);
754 new_query->size_banks = 1;
755 new_query->start_slot = static_cast<u32>(data_slot);
756 new_query->size_slots = 1;
757 pending_sync.push_back(index);
758 pending_flush_queries.push_back(index);
759 return index;
760 }
761
762 std::optional<std::pair<VAddr, size_t>> GetLastQueryStream(size_t stream) {
763 if (last_queries[stream] != 0) {
764 std::pair<VAddr, size_t> result(last_queries[stream], last_queries_stride[stream]);
765 return result;
766 }
767 return std::nullopt;
768 }
769
770 Maxwell3D::Regs::PrimitiveTopology GetOutputTopology() const {
771 return out_topology;
772 }
773
774 bool HasUnsyncedQueries() const override {
775 return !pending_flush_queries.empty();
776 }
777
778 void PushUnsyncedQueries() override {
779 CloseCounter();
780 auto staging_ref = staging_pool.Request(
781 pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
782 size_t offset_base = staging_ref.offset;
783 for (auto q : pending_flush_queries) {
784 auto* query = GetQuery(q);
785 auto& bank = bank_pool.GetBank(query->start_bank_id);
786 bank.Sync(staging_ref, offset_base, query->start_slot, 1);
787 offset_base += TFBQueryBank::QUERY_SIZE;
788 bank.CloseReference();
789 }
790 static constexpr VkMemoryBarrier WRITE_BARRIER{
791 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
792 .pNext = nullptr,
793 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
794 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
795 };
796 scheduler.RequestOutsideRenderPassOperationContext();
797 scheduler.Record([](vk::CommandBuffer cmdbuf) {
798 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
799 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
800 });
801
802 std::scoped_lock lk(flush_guard);
803 for (auto& str : free_queue) {
804 staging_pool.FreeDeferred(str);
805 }
806 free_queue.clear();
807 download_buffers.emplace_back(staging_ref);
808 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
809 }
810
811 void PopUnsyncedQueries() override {
812 StagingBufferRef staging_ref;
813 std::vector<size_t> flushed_queries;
814 {
815 std::scoped_lock lk(flush_guard);
816 staging_ref = download_buffers.front();
817 flushed_queries = std::move(pending_flush_sets.front());
818 download_buffers.pop_front();
819 pending_flush_sets.pop_front();
820 }
821
822 size_t offset_base = staging_ref.offset;
823 for (auto q : flushed_queries) {
824 auto* query = GetQuery(q);
825 u32 result = 0;
826 std::memcpy(&result, staging_ref.mapped_span.data() + offset_base, sizeof(u32));
827 query->value = static_cast<u64>(result);
828 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
829 offset_base += TFBQueryBank::QUERY_SIZE;
830 }
831
832 {
833 std::scoped_lock lk(flush_guard);
834 free_queue.emplace_back(staging_ref);
835 }
836 }
837
838private:
839 void FlushBeginTFB() {
840 if (has_flushed_end_pending) [[unlikely]] {
841 return;
842 }
843 has_flushed_end_pending = true;
844 if (!has_started || buffers_count == 0) {
845 scheduler.Record([](vk::CommandBuffer cmdbuf) {
846 cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
847 });
848 UpdateBuffers();
849 return;
850 }
851 scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
852 cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
853 });
854 UpdateBuffers();
855 }
856
857 void FlushEndTFB() {
858 if (!has_flushed_end_pending) [[unlikely]] {
859 UNREACHABLE();
860 return;
861 }
862 has_flushed_end_pending = false;
863
864 if (buffers_count == 0) {
865 scheduler.Record([](vk::CommandBuffer cmdbuf) {
866 cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr);
867 });
868 } else {
869 scheduler.Record([this,
870 total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
871 cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
872 });
873 }
874 }
875
876 void UpdateBuffers() {
877 last_queries.fill(0);
878 last_queries_stride.fill(1);
879 runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
880 buffers_count = 0;
881 out_topology = maxwell3d.draw_manager->GetDrawState().topology;
882 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
883 const auto& tf = maxwell3d.regs.transform_feedback;
884 if (tf.buffers[i].enable == 0) {
885 continue;
886 }
887 const size_t stream = tf.controls[i].stream;
888 last_queries_stride[stream] = tf.controls[i].stride;
889 streams_mask |= 1ULL << stream;
890 buffers_count = std::max<size_t>(buffers_count, stream + 1);
891 }
892 });
893 }
894
895 std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) {
896 if (current_bank == nullptr || current_bank->IsClosed()) {
897 current_bank_id =
898 bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) {
899 queue.emplace_back(scheduler, memory_allocator, index);
900 });
901 current_bank = &bank_pool.GetBank(current_bank_id);
902 }
903 auto [dont_care, other] = current_bank->Reserve();
904 const size_t slot = other; // workaround to compile bug.
905 current_bank->AddReference();
906
907 static constexpr VkMemoryBarrier READ_BARRIER{
908 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
909 .pNext = nullptr,
910 .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
911 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
912 };
913 static constexpr VkMemoryBarrier WRITE_BARRIER{
914 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
915 .pNext = nullptr,
916 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
917 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
918 };
919 scheduler.RequestOutsideRenderPassOperationContext();
920 scheduler.Record([dst_buffer = current_bank->GetBuffer(),
921 src_buffer = counter_buffers[stream], src_offset = offsets[stream],
922 slot](vk::CommandBuffer cmdbuf) {
923 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
924 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
925 std::array<VkBufferCopy, 1> copy{VkBufferCopy{
926 .srcOffset = src_offset,
927 .dstOffset = slot * TFBQueryBank::QUERY_SIZE,
928 .size = TFBQueryBank::QUERY_SIZE,
929 }};
930 cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
931 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
932 0, WRITE_BARRIER);
933 });
934 return {current_bank_id, slot};
935 }
936
937 friend class PrimitivesSucceededStreamer;
938
939 static constexpr size_t NUM_STREAMS = 4;
940
941 QueryCacheRuntime& runtime;
942 const Device& device;
943 Scheduler& scheduler;
944 const MemoryAllocator& memory_allocator;
945 StagingBufferPool& staging_pool;
946 VideoCommon::BankPool<TFBQueryBank> bank_pool;
947 size_t current_bank_id;
948 TFBQueryBank* current_bank;
949 vk::Buffer counters_buffer;
950
951 // syncing queue
952 std::vector<size_t> pending_sync;
953
954 // flush levels
955 std::vector<size_t> pending_flush_queries;
956 std::deque<StagingBufferRef> download_buffers;
957 std::deque<std::vector<size_t>> pending_flush_sets;
958 std::vector<StagingBufferRef> free_queue;
959 std::mutex flush_guard;
960
961 // state machine
962 bool has_started{};
963 bool has_flushed_end_pending{};
964 size_t buffers_count{};
965 std::array<VkBuffer, NUM_STREAMS> counter_buffers{};
966 std::array<VkDeviceSize, NUM_STREAMS> offsets{};
967 std::array<VAddr, NUM_STREAMS> last_queries;
968 std::array<size_t, NUM_STREAMS> last_queries_stride;
969 Maxwell3D::Regs::PrimitiveTopology out_topology;
970 u64 streams_mask;
971};
972
973class PrimitivesQueryBase : public VideoCommon::QueryBase {
974public:
975 // Default constructor
976 PrimitivesQueryBase()
977 : VideoCommon::QueryBase(0, VideoCommon::QueryFlagBits::IsHostManaged, 0) {}
978
979 // Parameterized constructor
980 PrimitivesQueryBase(bool has_timestamp, VAddr address)
981 : VideoCommon::QueryBase(address, VideoCommon::QueryFlagBits::IsHostManaged, 0) {
982 if (has_timestamp) {
983 flags |= VideoCommon::QueryFlagBits::HasTimestamp;
984 }
985 }
986
987 u64 stride{};
988 VAddr dependant_address{};
989 Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
990 size_t dependant_index{};
991 bool dependant_manage{};
992};
993
994class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> {
995public:
996 explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_,
997 TFBCounterStreamer& tfb_streamer_,
998 Core::Memory::Memory& cpu_memory_)
999 : VideoCommon::SimpleStreamer<PrimitivesQueryBase>(id_), runtime{runtime_},
1000 tfb_streamer{tfb_streamer_}, cpu_memory{cpu_memory_} {
1001 MakeDependent(&tfb_streamer);
1002 }
1003
1004 ~PrimitivesSucceededStreamer() = default;
1005
1006 size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
1007 std::optional<u32> subreport_) override {
1008 auto index = BuildQuery();
1009 auto* new_query = GetQuery(index);
1010 new_query->guest_address = address;
1011 new_query->value = 0;
1012 if (has_timestamp) {
1013 new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
1014 }
1015 if (!subreport_) {
1016 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1017 return index;
1018 }
1019 const size_t subreport = static_cast<size_t>(*subreport_);
1020 auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
1021 bool must_manage_dependance = false;
1022 new_query->topology = tfb_streamer.GetOutputTopology();
1023 if (dependant_address_opt) {
1024 auto [dep_address, stride] = *dependant_address_opt;
1025 new_query->dependant_address = dep_address;
1026 new_query->stride = stride;
1027 } else {
1028 new_query->dependant_index =
1029 tfb_streamer.WriteCounter(address, has_timestamp, value, subreport_);
1030 auto* dependant_query = tfb_streamer.GetQuery(new_query->dependant_index);
1031 dependant_query->flags |= VideoCommon::QueryFlagBits::IsInvalidated;
1032 must_manage_dependance = true;
1033 if (True(dependant_query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1034 new_query->value = 0;
1035 new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1036 if (must_manage_dependance) {
1037 tfb_streamer.Free(new_query->dependant_index);
1038 }
1039 return index;
1040 }
1041 new_query->stride = 1;
1042 runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
1043 for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
1044 const auto& tf = maxwell3d.regs.transform_feedback;
1045 if (tf.buffers[i].enable == 0) {
1046 continue;
1047 }
1048 if (tf.controls[i].stream != subreport) {
1049 continue;
1050 }
1051 new_query->stride = tf.controls[i].stride;
1052 break;
1053 }
1054 });
1055 }
1056
1057 new_query->dependant_manage = must_manage_dependance;
1058 pending_flush_queries.push_back(index);
1059 return index;
1060 }
1061
1062 bool HasUnsyncedQueries() const override {
1063 return !pending_flush_queries.empty();
1064 }
1065
1066 void PushUnsyncedQueries() override {
1067 std::scoped_lock lk(flush_guard);
1068 pending_flush_sets.emplace_back(std::move(pending_flush_queries));
1069 pending_flush_queries.clear();
1070 }
1071
1072 void PopUnsyncedQueries() override {
1073 std::vector<size_t> flushed_queries;
1074 {
1075 std::scoped_lock lk(flush_guard);
1076 flushed_queries = std::move(pending_flush_sets.front());
1077 pending_flush_sets.pop_front();
1078 }
1079
1080 for (auto q : flushed_queries) {
1081 auto* query = GetQuery(q);
1082 if (True(query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
1083 continue;
1084 }
1085
1086 query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
1087 u64 num_vertices = 0;
1088 if (query->dependant_manage) {
1089 auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index);
1090 num_vertices = dependant_query->value / query->stride;
1091 tfb_streamer.Free(query->dependant_index);
1092 } else {
1093 u8* pointer = cpu_memory.GetPointer(query->dependant_address);
1094 u32 result;
1095 std::memcpy(&result, pointer, sizeof(u32));
1096 num_vertices = static_cast<u64>(result) / query->stride;
1097 }
1098 query->value = [&]() -> u64 {
1099 switch (query->topology) {
1100 case Maxwell3D::Regs::PrimitiveTopology::Points:
1101 return num_vertices;
1102 case Maxwell3D::Regs::PrimitiveTopology::Lines:
1103 return num_vertices / 2;
1104 case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
1105 return (num_vertices / 2) + 1;
1106 case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
1107 return num_vertices - 1;
1108 case Maxwell3D::Regs::PrimitiveTopology::Patches:
1109 case Maxwell3D::Regs::PrimitiveTopology::Triangles:
1110 case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
1111 return num_vertices / 3;
1112 case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
1113 case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
1114 case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
1115 return num_vertices - 2;
1116 case Maxwell3D::Regs::PrimitiveTopology::Quads:
1117 return num_vertices / 4;
1118 case Maxwell3D::Regs::PrimitiveTopology::Polygon:
1119 return 1U;
1120 default:
1121 return num_vertices;
1122 }
1123 }();
1124 }
1125 }
1126
1127private:
1128 QueryCacheRuntime& runtime;
1129 TFBCounterStreamer& tfb_streamer;
1130 Core::Memory::Memory& cpu_memory;
1131
1132 // syncing queue
1133 std::vector<size_t> pending_sync;
1134
1135 // flush levels
1136 std::vector<size_t> pending_flush_queries;
1137 std::deque<std::vector<size_t>> pending_flush_sets;
1138 std::mutex flush_guard;
1139};
1140
1141} // namespace
1142
1143struct QueryCacheRuntimeImpl {
1144 QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_,
1145 Core::Memory::Memory& cpu_memory_, Vulkan::BufferCache& buffer_cache_,
1146 const Device& device_, const MemoryAllocator& memory_allocator_,
1147 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1148 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1149 DescriptorPool& descriptor_pool)
1150 : rasterizer{rasterizer_}, cpu_memory{cpu_memory_},
1151 buffer_cache{buffer_cache_}, device{device_},
1152 memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_},
1153 guest_streamer(0, runtime),
1154 sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
1155 device, scheduler, memory_allocator, compute_pass_descriptor_queue,
1156 descriptor_pool),
1157 tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
1158 scheduler, memory_allocator, staging_pool),
1159 primitives_succeeded_streamer(
1160 static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
1161 cpu_memory_),
1162 primitives_needed_minus_suceeded_streamer(
1163 static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
1164 hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
1165
1166 hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
1167 hcr_setup.pNext = nullptr;
1168 hcr_setup.flags = 0;
1169
1170 conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
1171 device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
1172
1173 const VkBufferCreateInfo buffer_ci = {
1174 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
1175 .pNext = nullptr,
1176 .flags = 0,
1177 .size = sizeof(u32),
1178 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
1179 VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
1180 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
1181 .queueFamilyIndexCount = 0,
1182 .pQueueFamilyIndices = nullptr,
1183 };
1184 hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
1185 }
1186
1187 VideoCore::RasterizerInterface* rasterizer;
1188 Core::Memory::Memory& cpu_memory;
1189 Vulkan::BufferCache& buffer_cache;
1190
1191 const Device& device;
1192 const MemoryAllocator& memory_allocator;
1193 Scheduler& scheduler;
1194 StagingBufferPool& staging_pool;
1195
1196 // Streamers
1197 VideoCommon::GuestStreamer<QueryCacheParams> guest_streamer;
1198 SamplesStreamer sample_streamer;
1199 TFBCounterStreamer tfb_streamer;
1200 PrimitivesSucceededStreamer primitives_succeeded_streamer;
1201 VideoCommon::StubStreamer<QueryCacheParams> primitives_needed_minus_suceeded_streamer;
1202
1203 std::vector<std::pair<VAddr, VAddr>> little_cache;
1204 std::vector<std::pair<VkBuffer, VkDeviceSize>> buffers_to_upload_to;
1205 std::vector<size_t> redirect_cache;
1206 std::vector<std::vector<VkBufferCopy>> copies_setup;
1207
1208 // Host conditional rendering data
1209 std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass;
1210 vk::Buffer hcr_resolve_buffer;
1211 VkConditionalRenderingBeginInfoEXT hcr_setup;
1212 VkBuffer hcr_buffer;
1213 size_t hcr_offset;
1214 bool hcr_is_set;
1215 bool is_hcr_running;
1216
1217 // maxwell3d
1218 Maxwell3D* maxwell3d;
1219};
1220
1221QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
1222 Core::Memory::Memory& cpu_memory_,
1223 Vulkan::BufferCache& buffer_cache_, const Device& device_,
1224 const MemoryAllocator& memory_allocator_,
1225 Scheduler& scheduler_, StagingBufferPool& staging_pool_,
1226 ComputePassDescriptorQueue& compute_pass_descriptor_queue,
1227 DescriptorPool& descriptor_pool) {
1228 impl = std::make_unique<QueryCacheRuntimeImpl>(
1229 *this, rasterizer, cpu_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
1230 staging_pool_, compute_pass_descriptor_queue, descriptor_pool);
42} 1231}
43 1232
44void QueryPool::Allocate(std::size_t begin, std::size_t end) { 1233void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) {
45 usage.resize(end); 1234 impl->maxwell3d = maxwell3d;
1235}
46 1236
47 pools.push_back(device.GetLogical().CreateQueryPool({ 1237template <typename Func>
48 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, 1238void QueryCacheRuntime::View3DRegs(Func&& func) {
49 .pNext = nullptr, 1239 if (impl->maxwell3d) {
50 .flags = 0, 1240 func(*impl->maxwell3d);
51 .queryType = GetTarget(type), 1241 }
52 .queryCount = static_cast<u32>(end - begin), 1242}
53 .pipelineStatistics = 0, 1243
54 })); 1244void QueryCacheRuntime::EndHostConditionalRendering() {
1245 PauseHostConditionalRendering();
1246 impl->hcr_is_set = false;
1247 impl->is_hcr_running = false;
1248 impl->hcr_buffer = nullptr;
1249 impl->hcr_offset = 0;
1250}
1251
1252void QueryCacheRuntime::PauseHostConditionalRendering() {
1253 if (!impl->hcr_is_set) {
1254 return;
1255 }
1256 if (impl->is_hcr_running) {
1257 impl->scheduler.Record(
1258 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndConditionalRenderingEXT(); });
1259 }
1260 impl->is_hcr_running = false;
55} 1261}
56 1262
57void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { 1263void QueryCacheRuntime::ResumeHostConditionalRendering() {
58 const auto it = 1264 if (!impl->hcr_is_set) {
59 std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) { 1265 return;
60 return query_pool == *pool; 1266 }
1267 if (!impl->is_hcr_running) {
1268 impl->scheduler.Record([hcr_setup = impl->hcr_setup](vk::CommandBuffer cmdbuf) {
1269 cmdbuf.BeginConditionalRenderingEXT(hcr_setup);
61 }); 1270 });
1271 }
1272 impl->is_hcr_running = true;
1273}
62 1274
63 if (it != std::end(pools)) { 1275void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object,
64 const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); 1276 bool is_equal) {
65 usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; 1277 {
1278 std::scoped_lock lk(impl->buffer_cache.mutex);
1279 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1280 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1281 const auto [buffer, offset] =
1282 impl->buffer_cache.ObtainCPUBuffer(object.address, 8, sync_info, post_op);
1283 impl->hcr_buffer = buffer->Handle();
1284 impl->hcr_offset = offset;
1285 }
1286 if (impl->hcr_is_set) {
1287 if (impl->hcr_setup.buffer == impl->hcr_buffer &&
1288 impl->hcr_setup.offset == impl->hcr_offset) {
1289 ResumeHostConditionalRendering();
1290 return;
1291 }
1292 PauseHostConditionalRendering();
66 } 1293 }
1294 impl->hcr_setup.buffer = impl->hcr_buffer;
1295 impl->hcr_setup.offset = impl->hcr_offset;
1296 impl->hcr_setup.flags = is_equal ? VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT : 0;
1297 impl->hcr_is_set = true;
1298 impl->is_hcr_running = false;
1299 ResumeHostConditionalRendering();
67} 1300}
68 1301
69QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, 1302void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal) {
70 Core::Memory::Memory& cpu_memory_, const Device& device_, 1303 VkBuffer to_resolve;
71 Scheduler& scheduler_) 1304 u32 to_resolve_offset;
72 : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_}, 1305 {
73 query_pools{ 1306 std::scoped_lock lk(impl->buffer_cache.mutex);
74 QueryPool{device_, scheduler_, QueryType::SamplesPassed}, 1307 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize;
75 } {} 1308 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
76 1309 const auto [buffer, offset] =
77QueryCache::~QueryCache() { 1310 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 1311 to_resolve = buffer->Handle();
79 // destructor is called. The query cache should be redesigned to have a proper ownership model 1312 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 } 1313 }
1314 if (impl->is_hcr_running) {
1315 PauseHostConditionalRendering();
1316 }
1317 impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve,
1318 to_resolve_offset, false);
1319 impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
1320 impl->hcr_setup.offset = 0;
1321 impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
1322 impl->hcr_is_set = true;
1323 impl->is_hcr_running = false;
1324 ResumeHostConditionalRendering();
86} 1325}
87 1326
88std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) { 1327bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1,
89 return query_pools[static_cast<std::size_t>(type)].Commit(); 1328 [[maybe_unused]] bool qc_dirty) {
1329 if (!impl->device.IsExtConditionalRendering()) {
1330 return false;
1331 }
1332 HostConditionalRenderingCompareValueImpl(object_1, false);
1333 return true;
90} 1334}
91 1335
92void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) { 1336bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
93 query_pools[static_cast<std::size_t>(type)].Reserve(query); 1337 VideoCommon::LookupData object_2,
1338 bool qc_dirty, bool equal_check) {
1339 if (!impl->device.IsExtConditionalRendering()) {
1340 return false;
1341 }
1342
1343 const auto check_in_bc = [&](VAddr address) {
1344 return impl->buffer_cache.IsRegionGpuModified(address, 8);
1345 };
1346 const auto check_value = [&](VAddr address) {
1347 u8* ptr = impl->cpu_memory.GetPointer(address);
1348 u64 value{};
1349 std::memcpy(&value, ptr, sizeof(value));
1350 return value == 0;
1351 };
1352 std::array<VideoCommon::LookupData*, 2> objects{&object_1, &object_2};
1353 std::array<bool, 2> is_in_bc{};
1354 std::array<bool, 2> is_in_qc{};
1355 std::array<bool, 2> is_in_ac{};
1356 std::array<bool, 2> is_null{};
1357 {
1358 std::scoped_lock lk(impl->buffer_cache.mutex);
1359 for (size_t i = 0; i < 2; i++) {
1360 is_in_qc[i] = objects[i]->found_query != nullptr;
1361 is_in_bc[i] = !is_in_qc[i] && check_in_bc(objects[i]->address);
1362 is_in_ac[i] = is_in_qc[i] || is_in_bc[i];
1363 }
1364 }
1365
1366 if (!is_in_ac[0] && !is_in_ac[1]) {
1367 EndHostConditionalRendering();
1368 return false;
1369 }
1370
1371 if (!qc_dirty && !is_in_bc[0] && !is_in_bc[1]) {
1372 EndHostConditionalRendering();
1373 return false;
1374 }
1375
1376 const bool is_gpu_high = Settings::IsGPULevelHigh();
1377 if (!is_gpu_high && impl->device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
1378 return true;
1379 }
1380
1381 for (size_t i = 0; i < 2; i++) {
1382 is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
1383 }
1384
1385 for (size_t i = 0; i < 2; i++) {
1386 if (is_null[i]) {
1387 size_t j = (i + 1) % 2;
1388 HostConditionalRenderingCompareValueImpl(*objects[j], equal_check);
1389 return true;
1390 }
1391 }
1392
1393 if (!is_gpu_high) {
1394 return true;
1395 }
1396
1397 if (!is_in_bc[0] && !is_in_bc[1]) {
1398 // Both queries are in query cache, it's best to just flush.
1399 return true;
1400 }
1401 HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
1402 return true;
94} 1403}
95 1404
96HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, 1405QueryCacheRuntime::~QueryCacheRuntime() = default;
97 QueryType type_) 1406
98 : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, 1407VideoCommon::StreamerInterface* QueryCacheRuntime::GetStreamerInterface(QueryType query_type) {
99 query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} { 1408 switch (query_type) {
100 const vk::Device* logical = &cache.GetDevice().GetLogical(); 1409 case QueryType::Payload:
101 cache.GetScheduler().Record([logical, query_ = query](vk::CommandBuffer cmdbuf) { 1410 return &impl->guest_streamer;
102 const bool use_precise = Settings::IsGPULevelHigh(); 1411 case QueryType::ZPassPixelCount64:
103 logical->ResetQueryPool(query_.first, query_.second, 1); 1412 return &impl->sample_streamer;
104 cmdbuf.BeginQuery(query_.first, query_.second, 1413 case QueryType::StreamingByteCount:
105 use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); 1414 return &impl->tfb_streamer;
106 }); 1415 case QueryType::StreamingPrimitivesNeeded:
1416 case QueryType::VtgPrimitivesOut:
1417 case QueryType::StreamingPrimitivesSucceeded:
1418 return &impl->primitives_succeeded_streamer;
1419 case QueryType::StreamingPrimitivesNeededMinusSucceeded:
1420 return &impl->primitives_needed_minus_suceeded_streamer;
1421 default:
1422 return nullptr;
1423 }
107} 1424}
108 1425
109HostCounter::~HostCounter() { 1426void QueryCacheRuntime::Barriers(bool is_prebarrier) {
110 cache.Reserve(type, query); 1427 static constexpr VkMemoryBarrier READ_BARRIER{
1428 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1429 .pNext = nullptr,
1430 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
1431 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
1432 };
1433 static constexpr VkMemoryBarrier WRITE_BARRIER{
1434 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
1435 .pNext = nullptr,
1436 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
1437 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
1438 };
1439 if (is_prebarrier) {
1440 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1441 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
1442 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
1443 });
1444 } else {
1445 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1446 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
1447 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
1448 });
1449 }
111} 1450}
112 1451
113void HostCounter::EndQuery() { 1452template <typename SyncValuesType>
114 cache.GetScheduler().Record([query_ = query](vk::CommandBuffer cmdbuf) { 1453void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer) {
115 cmdbuf.EndQuery(query_.first, query_.second); 1454 if (values.size() == 0) {
1455 return;
1456 }
1457 impl->redirect_cache.clear();
1458 impl->little_cache.clear();
1459 size_t total_size = 0;
1460 for (auto& sync_val : values) {
1461 total_size += sync_val.size;
1462 bool found = false;
1463 VAddr base = Common::AlignDown(sync_val.address, Core::Memory::YUZU_PAGESIZE);
1464 VAddr base_end = base + Core::Memory::YUZU_PAGESIZE;
1465 for (size_t i = 0; i < impl->little_cache.size(); i++) {
1466 const auto set_found = [&] {
1467 impl->redirect_cache.push_back(i);
1468 found = true;
1469 };
1470 auto& loc = impl->little_cache[i];
1471 if (base < loc.second && loc.first < base_end) {
1472 set_found();
1473 break;
1474 }
1475 if (loc.first == base_end) {
1476 loc.first = base;
1477 set_found();
1478 break;
1479 }
1480 if (loc.second == base) {
1481 loc.second = base_end;
1482 set_found();
1483 break;
1484 }
1485 }
1486 if (!found) {
1487 impl->redirect_cache.push_back(impl->little_cache.size());
1488 impl->little_cache.emplace_back(base, base_end);
1489 }
1490 }
1491
1492 // Vulkan part.
1493 std::scoped_lock lk(impl->buffer_cache.mutex);
1494 impl->buffer_cache.BufferOperations([&] {
1495 impl->buffers_to_upload_to.clear();
1496 for (auto& pair : impl->little_cache) {
1497 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1498 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
1499 const auto [buffer, offset] = impl->buffer_cache.ObtainCPUBuffer(
1500 pair.first, static_cast<u32>(pair.second - pair.first), sync_info, post_op);
1501 impl->buffers_to_upload_to.emplace_back(buffer->Handle(), offset);
1502 }
116 }); 1503 });
117}
118 1504
119u64 HostCounter::BlockingQuery(bool async) const { 1505 VkBuffer src_buffer;
120 if (!async) { 1506 [[maybe_unused]] StagingBufferRef ref;
121 cache.GetScheduler().Wait(tick); 1507 impl->copies_setup.clear();
122 } 1508 impl->copies_setup.resize(impl->little_cache.size());
123 u64 data; 1509 if constexpr (SyncValuesType::GeneratesBaseBuffer) {
124 const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( 1510 ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload);
125 query.first, query.second, 1, sizeof(data), &data, sizeof(data), 1511 size_t current_offset = ref.offset;
126 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); 1512 size_t accumulated_size = 0;
127 1513 for (size_t i = 0; i < values.size(); i++) {
128 switch (query_result) { 1514 size_t which_copy = impl->redirect_cache[i];
129 case VK_SUCCESS: 1515 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
130 return data; 1516 .srcOffset = current_offset + accumulated_size,
131 case VK_ERROR_DEVICE_LOST: 1517 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
132 cache.GetDevice().ReportLoss(); 1518 impl->little_cache[which_copy].first,
133 [[fallthrough]]; 1519 .size = values[i].size,
134 default: 1520 });
135 throw vk::Exception(query_result); 1521 std::memcpy(ref.mapped_span.data() + accumulated_size, &values[i].value,
1522 values[i].size);
1523 accumulated_size += values[i].size;
1524 }
1525 src_buffer = ref.buffer;
1526 } else {
1527 for (size_t i = 0; i < values.size(); i++) {
1528 size_t which_copy = impl->redirect_cache[i];
1529 impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
1530 .srcOffset = values[i].offset,
1531 .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
1532 impl->little_cache[which_copy].first,
1533 .size = values[i].size,
1534 });
1535 }
1536 src_buffer = base_src_buffer;
136 } 1537 }
1538
1539 impl->scheduler.RequestOutsideRenderPassOperationContext();
1540 impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to),
1541 vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
1542 size_t size = dst_buffers.size();
1543 for (size_t i = 0; i < size; i++) {
1544 cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);
1545 }
1546 });
137} 1547}
138 1548
139} // namespace Vulkan 1549} // namespace Vulkan
1550
1551namespace VideoCommon {
1552
1553template class QueryCacheBase<Vulkan::QueryCacheParams>;
1554
1555} // 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..83f2b6045 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 ||
@@ -429,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
429 return; 422 return;
430 } 423 }
431 424
432 if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { 425 if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
426 regs.stencil_front_mask != 0) {
433 Region2D dst_region = { 427 Region2D dst_region = {
434 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, 428 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
435 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), 429 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
@@ -482,13 +476,13 @@ void RasterizerVulkan::DispatchCompute() {
482 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); 476 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
483} 477}
484 478
485void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) { 479void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
486 query_cache.ResetCounter(type); 480 query_cache.CounterReset(type);
487} 481}
488 482
489void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, 483void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
490 std::optional<u64> timestamp) { 484 VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
491 query_cache.Query(gpu_addr, type, timestamp); 485 query_cache.CounterReport(gpu_addr, type, flags, payload, subreport);
492} 486}
493 487
494void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 488void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -669,8 +663,8 @@ void RasterizerVulkan::SignalReference() {
669 fence_manager.SignalReference(); 663 fence_manager.SignalReference();
670} 664}
671 665
672void RasterizerVulkan::ReleaseFences() { 666void RasterizerVulkan::ReleaseFences(bool force) {
673 fence_manager.WaitPendingFences(); 667 fence_manager.WaitPendingFences(force);
674} 668}
675 669
676void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size, 670void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size,
@@ -694,6 +688,8 @@ void RasterizerVulkan::WaitForIdle() {
694 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; 688 flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
695 } 689 }
696 690
691 query_cache.NotifyWFI();
692
697 scheduler.RequestOutsideRenderPassOperationContext(); 693 scheduler.RequestOutsideRenderPassOperationContext();
698 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) { 694 scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) {
699 cmdbuf.SetEvent(event, flags); 695 cmdbuf.SetEvent(event, flags);
@@ -737,19 +733,7 @@ void RasterizerVulkan::TickFrame() {
737 733
738bool RasterizerVulkan::AccelerateConditionalRendering() { 734bool RasterizerVulkan::AccelerateConditionalRendering() {
739 gpu_memory->FlushCaching(); 735 gpu_memory->FlushCaching();
740 if (Settings::IsGPULevelHigh()) { 736 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} 737}
754 738
755bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, 739bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
@@ -795,6 +779,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
795 if (!image_view) { 779 if (!image_view) {
796 return false; 780 return false;
797 } 781 }
782 query_cache.NotifySegment(false);
798 screen_info.image = image_view->ImageHandle(); 783 screen_info.image = image_view->ImageHandle();
799 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D); 784 screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D);
800 screen_info.width = image_view->size.width; 785 screen_info.width = image_view->size.width;
@@ -933,31 +918,18 @@ void RasterizerVulkan::UpdateDynamicStates() {
933 } 918 }
934} 919}
935 920
936void RasterizerVulkan::BeginTransformFeedback() { 921void RasterizerVulkan::HandleTransformFeedback() {
937 const auto& regs = maxwell3d->regs; 922 const auto& regs = maxwell3d->regs;
938 if (regs.transform_feedback_enabled == 0) {
939 return;
940 }
941 if (!device.IsExtTransformFeedbackSupported()) { 923 if (!device.IsExtTransformFeedbackSupported()) {
942 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); 924 LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
943 return; 925 return;
944 } 926 }
945 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || 927 query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
946 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); 928 regs.transform_feedback_enabled);
947 scheduler.Record( 929 if (regs.transform_feedback_enabled != 0) {
948 [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); 930 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
949} 931 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 } 932 }
959 scheduler.Record(
960 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
961} 933}
962 934
963void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { 935void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
@@ -1043,15 +1015,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
1043 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || 1015 regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM ||
1044 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || 1016 regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM ||
1045 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; 1017 regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM;
1046 if (is_d24 && !device.SupportsD24DepthBuffer()) { 1018 bool force_unorm = ([&] {
1019 if (!is_d24 || device.SupportsD24DepthBuffer()) {
1020 return false;
1021 }
1022 if (device.IsExtDepthBiasControlSupported()) {
1023 return true;
1024 }
1025 if (!Settings::values.renderer_amdvlk_depth_bias_workaround) {
1026 return false;
1027 }
1047 // the base formulas can be obtained from here: 1028 // 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 1029 // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
1049 const double rescale_factor = 1030 const double rescale_factor =
1050 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127)); 1031 static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127));
1051 units = static_cast<float>(static_cast<double>(units) * rescale_factor); 1032 units = static_cast<float>(static_cast<double>(units) * rescale_factor);
1052 } 1033 return false;
1034 })();
1053 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, 1035 scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
1054 factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) { 1036 factor = regs.slope_scale_depth_bias, force_unorm,
1037 precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) {
1038 if (force_unorm) {
1039 VkDepthBiasRepresentationInfoEXT info{
1040 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT,
1041 .pNext = nullptr,
1042 .depthBiasRepresentation =
1043 VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT,
1044 .depthBiasExact = precise ? VK_TRUE : VK_FALSE,
1045 };
1046 cmdbuf.SetDepthBias(constant, clamp, factor, &info);
1047 return;
1048 }
1055 cmdbuf.SetDepthBias(constant, clamp, factor); 1049 cmdbuf.SetDepthBias(constant, clamp, factor);
1056 }); 1050 });
1057} 1051}
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_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index ce92f66ab..b278614e6 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -24,25 +24,38 @@ using namespace Common::Literals;
24 24
25// Maximum potential alignment of a Vulkan buffer 25// Maximum potential alignment of a Vulkan buffer
26constexpr VkDeviceSize MAX_ALIGNMENT = 256; 26constexpr VkDeviceSize MAX_ALIGNMENT = 256;
27// Maximum size to put elements in the stream buffer
28constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
29// Stream buffer size in bytes 27// Stream buffer size in bytes
30constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB; 28constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
31constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
32 29
33size_t Region(size_t iterator) noexcept { 30size_t GetStreamBufferSize(const Device& device) {
34 return iterator / REGION_SIZE; 31 VkDeviceSize size{0};
32 if (device.HasDebuggingToolAttached()) {
33 ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
34 size = std::max(size, heap.size);
35 });
36 // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
37 // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
38 // as the heap will be much larger.
39 if (size <= 256_MiB) {
40 size = size * 40 / 100;
41 }
42 } else {
43 size = MAX_STREAM_BUFFER_SIZE;
44 }
45 return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
35} 46}
36} // Anonymous namespace 47} // Anonymous namespace
37 48
38StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, 49StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
39 Scheduler& scheduler_) 50 Scheduler& scheduler_)
40 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { 51 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
52 stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
53 StagingBufferPool::NUM_SYNCS} {
41 VkBufferCreateInfo stream_ci = { 54 VkBufferCreateInfo stream_ci = {
42 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 55 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
43 .pNext = nullptr, 56 .pNext = nullptr,
44 .flags = 0, 57 .flags = 0,
45 .size = STREAM_BUFFER_SIZE, 58 .size = stream_buffer_size,
46 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | 59 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
47 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 60 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
48 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 61 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
63StagingBufferPool::~StagingBufferPool() = default; 76StagingBufferPool::~StagingBufferPool() = default;
64 77
65StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { 78StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
66 if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) { 79 if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
67 return GetStreamBuffer(size); 80 return GetStreamBuffer(size);
68 } 81 }
69 return GetStagingBuffer(size, usage, deferred); 82 return GetStagingBuffer(size, usage, deferred);
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
101 used_iterator = iterator; 114 used_iterator = iterator;
102 free_iterator = std::max(free_iterator, iterator + size); 115 free_iterator = std::max(free_iterator, iterator + size);
103 116
104 if (iterator + size >= STREAM_BUFFER_SIZE) { 117 if (iterator + size >= stream_buffer_size) {
105 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, 118 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
106 current_tick); 119 current_tick);
107 used_iterator = 0; 120 used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 5f69f08b1..d3deb9072 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -90,6 +90,9 @@ private:
90 void ReleaseCache(MemoryUsage usage); 90 void ReleaseCache(MemoryUsage usage);
91 91
92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2); 92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
93 size_t Region(size_t iter) const noexcept {
94 return iter / region_size;
95 }
93 96
94 const Device& device; 97 const Device& device;
95 MemoryAllocator& memory_allocator; 98 MemoryAllocator& memory_allocator;
@@ -97,6 +100,8 @@ private:
97 100
98 vk::Buffer stream_buffer; 101 vk::Buffer stream_buffer;
99 std::span<u8> stream_pointer; 102 std::span<u8> stream_pointer;
103 VkDeviceSize stream_buffer_size;
104 VkDeviceSize region_size;
100 105
101 size_t iterator = 0; 106 size_t iterator = 0;
102 size_t used_iterator = 0; 107 size_t used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index b3e17c332..00ab47268 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:
@@ -218,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
218 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; 238 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
219 case PixelFormat::D16_UNORM: 239 case PixelFormat::D16_UNORM:
220 case PixelFormat::D32_FLOAT: 240 case PixelFormat::D32_FLOAT:
241 case PixelFormat::X8_D24_UNORM:
221 return VK_IMAGE_ASPECT_DEPTH_BIT; 242 return VK_IMAGE_ASPECT_DEPTH_BIT;
222 case PixelFormat::S8_UINT: 243 case PixelFormat::S8_UINT:
223 return VK_IMAGE_ASPECT_STENCIL_BIT; 244 return VK_IMAGE_ASPECT_STENCIL_BIT;
@@ -600,7 +621,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
600} 621}
601 622
602void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, 623void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
603 bool emulate_bgr565) { 624 bool emulate_bgr565, bool emulate_a4b4g4r4) {
604 switch (format) { 625 switch (format) {
605 case PixelFormat::A1B5G5R5_UNORM: 626 case PixelFormat::A1B5G5R5_UNORM:
606 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); 627 std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@@ -616,6 +637,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
616 case PixelFormat::G4R4_UNORM: 637 case PixelFormat::G4R4_UNORM:
617 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); 638 std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
618 break; 639 break;
640 case PixelFormat::A4B4G4R4_UNORM:
641 if (emulate_a4b4g4r4) {
642 std::ranges::reverse(swizzle);
643 }
644 break;
619 default: 645 default:
620 break; 646 break;
621 } 647 }
@@ -822,6 +848,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
822 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, 848 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
823 compute_pass_descriptor_queue, memory_allocator); 849 compute_pass_descriptor_queue, memory_allocator);
824 } 850 }
851 if (device.IsStorageImageMultisampleSupported()) {
852 msaa_copy_pass = std::make_unique<MSAACopyPass>(
853 device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
854 }
825 if (!device.IsKhrImageFormatListSupported()) { 855 if (!device.IsKhrImageFormatListSupported()) {
826 return; 856 return;
827 } 857 }
@@ -1044,15 +1074,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
1044 dst_region, src_region, filter, operation); 1074 dst_region, src_region, filter, operation);
1045 return; 1075 return;
1046 } 1076 }
1077 ASSERT(src.format == dst.format);
1047 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { 1078 if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1048 if (!device.IsBlitDepthStencilSupported()) { 1079 const auto format = src.format;
1080 const auto can_blit_depth_stencil = [this, format] {
1081 switch (format) {
1082 case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
1083 case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
1084 return device.IsBlitDepth24Stencil8Supported();
1085 case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
1086 return device.IsBlitDepth32Stencil8Supported();
1087 default:
1088 UNREACHABLE();
1089 }
1090 }();
1091 if (!can_blit_depth_stencil) {
1049 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); 1092 UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
1050 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), 1093 blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
1051 dst_region, src_region, filter, operation); 1094 dst_region, src_region, filter, operation);
1052 return; 1095 return;
1053 } 1096 }
1054 } 1097 }
1055 ASSERT(src.format == dst.format);
1056 ASSERT(!(is_dst_msaa && !is_src_msaa)); 1098 ASSERT(!(is_dst_msaa && !is_src_msaa));
1057 ASSERT(operation == Fermi2D::Operation::SrcCopy); 1099 ASSERT(operation == Fermi2D::Operation::SrcCopy);
1058 1100
@@ -1159,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
1159 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { 1201 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
1160 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); 1202 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
1161 } 1203 }
1204 if (src_view.format == PixelFormat::D32_FLOAT) {
1205 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1206 }
1162 break; 1207 break;
1163 case PixelFormat::R32_FLOAT: 1208 case PixelFormat::R32_FLOAT:
1164 if (src_view.format == PixelFormat::D32_FLOAT) { 1209 if (src_view.format == PixelFormat::D32_FLOAT) {
@@ -1278,7 +1323,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
1278 1323
1279void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, 1324void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
1280 std::span<const VideoCommon::ImageCopy> copies) { 1325 std::span<const VideoCommon::ImageCopy> copies) {
1281 UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan."); 1326 const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
1327 if (msaa_copy_pass) {
1328 return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa);
1329 }
1330 UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
1282} 1331}
1283 1332
1284u64 TextureCacheRuntime::GetDeviceLocalMemory() const { 1333u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
@@ -1326,39 +1375,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
1326 if (runtime->device.HasDebuggingToolAttached()) { 1375 if (runtime->device.HasDebuggingToolAttached()) {
1327 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); 1376 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
1328 } 1377 }
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; 1378 current_image = *original_image;
1379 storage_image_views.resize(info.resources.levels);
1335 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && 1380 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
1336 Settings::values.astc_recompression.GetValue() == 1381 Settings::values.astc_recompression.GetValue() ==
1337 Settings::AstcRecompression::Uncompressed) { 1382 Settings::AstcRecompression::Uncompressed) {
1338 const auto& device = runtime->device.GetLogical(); 1383 const auto& device = runtime->device.GetLogical();
1339 storage_image_views.reserve(info.resources.levels);
1340 for (s32 level = 0; level < info.resources.levels; ++level) { 1384 for (s32 level = 0; level < info.resources.levels; ++level) {
1341 storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{ 1385 storage_image_views[level] =
1342 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1386 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 } 1387 }
1363 } 1388 }
1364} 1389}
@@ -1489,6 +1514,17 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
1489 DownloadMemory(buffers, offsets, copies); 1514 DownloadMemory(buffers, offsets, copies);
1490} 1515}
1491 1516
1517VkImageView Image::StorageImageView(s32 level) noexcept {
1518 auto& view = storage_image_views[level];
1519 if (!view) {
1520 const auto format_info =
1521 MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
1522 view =
1523 MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format);
1524 }
1525 return *view;
1526}
1527
1492bool Image::IsRescaled() const noexcept { 1528bool Image::IsRescaled() const noexcept {
1493 return True(flags & ImageFlagBits::Rescaled); 1529 return True(flags & ImageFlagBits::Rescaled);
1494} 1530}
@@ -1626,8 +1662,8 @@ bool Image::NeedsScaleHelper() const {
1626 return true; 1662 return true;
1627 } 1663 }
1628 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; 1664 static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal;
1629 const PixelFormat format = StorageFormat(info.format); 1665 const auto vk_format =
1630 const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format; 1666 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; 1667 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); 1668 const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT);
1633 return needs_blit_helper; 1669 return needs_blit_helper;
@@ -1649,7 +1685,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1649 }; 1685 };
1650 if (!info.IsRenderTarget()) { 1686 if (!info.IsRenderTarget()) {
1651 swizzle = info.Swizzle(); 1687 swizzle = info.Swizzle();
1652 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); 1688 TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
1689 !device->IsExt4444FormatsSupported());
1653 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { 1690 if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
1654 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); 1691 std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
1655 } 1692 }
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/surface.cpp b/src/video_core/surface.cpp
index e16cd5e73..5b3c7aa5a 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
85 return PixelFormat::S8_UINT; 85 return PixelFormat::S8_UINT;
86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT: 86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
87 return PixelFormat::D32_FLOAT_S8_UINT; 87 return PixelFormat::D32_FLOAT_S8_UINT;
88 case Tegra::DepthFormat::X8Z24_UNORM:
89 return PixelFormat::X8_D24_UNORM;
88 default: 90 default:
89 UNIMPLEMENTED_MSG("Unimplemented format={}", format); 91 UNIMPLEMENTED_MSG("Unimplemented format={}", format);
90 return PixelFormat::S8_UINT_D24_UNORM; 92 return PixelFormat::S8_UINT_D24_UNORM;
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
202PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) { 204PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
203 switch (format) { 205 switch (format) {
204 case Service::android::PixelFormat::Rgba8888: 206 case Service::android::PixelFormat::Rgba8888:
207 case Service::android::PixelFormat::Rgbx8888:
205 return PixelFormat::A8B8G8R8_UNORM; 208 return PixelFormat::A8B8G8R8_UNORM;
206 case Service::android::PixelFormat::Rgb565: 209 case Service::android::PixelFormat::Rgb565:
207 return PixelFormat::R5G6B5_UNORM; 210 return PixelFormat::R5G6B5_UNORM;
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 9b9c4d9bc..a5e8e2f62 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -115,6 +115,7 @@ enum class PixelFormat {
115 // Depth formats 115 // Depth formats
116 D32_FLOAT = MaxColorFormat, 116 D32_FLOAT = MaxColorFormat,
117 D16_UNORM, 117 D16_UNORM,
118 X8_D24_UNORM,
118 119
119 MaxDepthFormat, 120 MaxDepthFormat,
120 121
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
251 1, // E5B9G9R9_FLOAT 252 1, // E5B9G9R9_FLOAT
252 1, // D32_FLOAT 253 1, // D32_FLOAT
253 1, // D16_UNORM 254 1, // D16_UNORM
255 1, // X8_D24_UNORM
254 1, // S8_UINT 256 1, // S8_UINT
255 1, // D24_UNORM_S8_UINT 257 1, // D24_UNORM_S8_UINT
256 1, // S8_UINT_D24_UNORM 258 1, // S8_UINT_D24_UNORM
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
360 1, // E5B9G9R9_FLOAT 362 1, // E5B9G9R9_FLOAT
361 1, // D32_FLOAT 363 1, // D32_FLOAT
362 1, // D16_UNORM 364 1, // D16_UNORM
365 1, // X8_D24_UNORM
363 1, // S8_UINT 366 1, // S8_UINT
364 1, // D24_UNORM_S8_UINT 367 1, // D24_UNORM_S8_UINT
365 1, // S8_UINT_D24_UNORM 368 1, // S8_UINT_D24_UNORM
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
469 32, // E5B9G9R9_FLOAT 472 32, // E5B9G9R9_FLOAT
470 32, // D32_FLOAT 473 32, // D32_FLOAT
471 16, // D16_UNORM 474 16, // D16_UNORM
475 32, // X8_D24_UNORM
472 8, // S8_UINT 476 8, // S8_UINT
473 32, // D24_UNORM_S8_UINT 477 32, // D24_UNORM_S8_UINT
474 32, // S8_UINT_D24_UNORM 478 32, // S8_UINT_D24_UNORM
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 11ced6c38..8c774f512 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -138,8 +138,16 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
138 return PixelFormat::E5B9G9R9_FLOAT; 138 return PixelFormat::E5B9G9R9_FLOAT;
139 case Hash(TextureFormat::Z32, FLOAT): 139 case Hash(TextureFormat::Z32, FLOAT):
140 return PixelFormat::D32_FLOAT; 140 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
142 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z16, UNORM): 143 case Hash(TextureFormat::Z16, UNORM):
142 return PixelFormat::D16_UNORM; 144 return PixelFormat::D16_UNORM;
145 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
146 return PixelFormat::D16_UNORM;
147 case Hash(TextureFormat::X8Z24, UNORM):
148 return PixelFormat::X8_D24_UNORM;
149 case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
150 return PixelFormat::X8_D24_UNORM;
143 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): 151 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
144 return PixelFormat::S8_UINT_D24_UNORM; 152 return PixelFormat::S8_UINT_D24_UNORM;
145 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): 153 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index 9ee57a076..cabbfcb2d 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
211 return "D32_FLOAT"; 211 return "D32_FLOAT";
212 case PixelFormat::D16_UNORM: 212 case PixelFormat::D16_UNORM:
213 return "D16_UNORM"; 213 return "D16_UNORM";
214 case PixelFormat::X8_D24_UNORM:
215 return "X8_D24_UNORM";
214 case PixelFormat::S8_UINT: 216 case PixelFormat::S8_UINT:
215 return "S8_UINT"; 217 return "S8_UINT";
216 case PixelFormat::D24_UNORM_S8_UINT: 218 case PixelFormat::D24_UNORM_S8_UINT:
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 55d49d017..0587d7b72 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -41,7 +41,7 @@ enum class ImageFlagBits : u32 {
41 IsRescalable = 1 << 15, 41 IsRescalable = 1 << 15,
42 42
43 AsynchronousDecode = 1 << 16, 43 AsynchronousDecode = 1 << 16,
44 IsDecoding = 1 << 17, ///< Is currently being decoded asynchornously. 44 IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
45}; 45};
46DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) 46DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
47 47
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index 0c5f4450d..18b9250f9 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
85 // Depth formats 85 // Depth formats
86 case PixelFormat::D32_FLOAT: 86 case PixelFormat::D32_FLOAT:
87 case PixelFormat::D16_UNORM: 87 case PixelFormat::D16_UNORM:
88 case PixelFormat::X8_D24_UNORM:
88 // Stencil formats 89 // Stencil formats
89 case PixelFormat::S8_UINT: 90 case PixelFormat::S8_UINT:
90 // DepthStencil formats 91 // DepthStencil formats
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 0f8ef4277..2e8160db0 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -1194,7 +1194,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1194 return std::nullopt; 1194 return std::nullopt;
1195 } 1195 }
1196 } else { 1196 } else {
1197 // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format 1197 // Format compatibility is not relaxed, ensure we are creating a view on a compatible format
1198 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { 1198 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
1199 return std::nullopt; 1199 return std::nullopt;
1200 } 1200 }
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 617417040..876cec2e8 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -76,12 +76,20 @@ 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 {
82 AmpereOrNewer, 87 KeplerOrOlder,
88 Maxwell,
89 Pascal,
90 Volta,
83 Turing, 91 Turing,
84 VoltaOrOlder, 92 AmpereOrNewer,
85}; 93};
86 94
87template <typename T> 95template <typename T>
@@ -110,6 +118,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
110 return Alternatives::R8G8B8_SSCALED.data(); 118 return Alternatives::R8G8B8_SSCALED.data();
111 case VK_FORMAT_R32G32B32_SFLOAT: 119 case VK_FORMAT_R32G32B32_SFLOAT:
112 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); 120 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
121 case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
122 return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
113 default: 123 default:
114 return nullptr; 124 return nullptr;
115 } 125 }
@@ -193,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
193 VK_FORMAT_BC7_UNORM_BLOCK, 203 VK_FORMAT_BC7_UNORM_BLOCK,
194 VK_FORMAT_D16_UNORM, 204 VK_FORMAT_D16_UNORM,
195 VK_FORMAT_D16_UNORM_S8_UINT, 205 VK_FORMAT_D16_UNORM_S8_UINT,
206 VK_FORMAT_X8_D24_UNORM_PACK32,
196 VK_FORMAT_D24_UNORM_S8_UINT, 207 VK_FORMAT_D24_UNORM_S8_UINT,
197 VK_FORMAT_D32_SFLOAT, 208 VK_FORMAT_D32_SFLOAT,
198 VK_FORMAT_D32_SFLOAT_S8_UINT, 209 VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -238,6 +249,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
238 VK_FORMAT_R32_SINT, 249 VK_FORMAT_R32_SINT,
239 VK_FORMAT_R32_UINT, 250 VK_FORMAT_R32_UINT,
240 VK_FORMAT_R4G4B4A4_UNORM_PACK16, 251 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
252 VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
241 VK_FORMAT_R4G4_UNORM_PACK8, 253 VK_FORMAT_R4G4_UNORM_PACK8,
242 VK_FORMAT_R5G5B5A1_UNORM_PACK16, 254 VK_FORMAT_R5G5B5A1_UNORM_PACK16,
243 VK_FORMAT_R5G6B5_UNORM_PACK16, 255 VK_FORMAT_R5G6B5_UNORM_PACK16,
@@ -313,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
313 physical.GetProperties2(physical_properties); 325 physical.GetProperties2(physical_properties);
314 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { 326 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
315 // Only Ampere and newer support this feature 327 // Only Ampere and newer support this feature
328 // TODO: Find a way to differentiate Ampere and Ada
316 return NvidiaArchitecture::AmpereOrNewer; 329 return NvidiaArchitecture::AmpereOrNewer;
317 } 330 }
318 }
319 if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
320 return NvidiaArchitecture::Turing; 331 return NvidiaArchitecture::Turing;
321 } 332 }
322 return NvidiaArchitecture::VoltaOrOlder; 333
334 if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
335 VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
336 advanced_blending_props.sType =
337 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
338 VkPhysicalDeviceProperties2 physical_properties{};
339 physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
340 physical_properties.pNext = &advanced_blending_props;
341 physical.GetProperties2(physical_properties);
342 if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
343 return NvidiaArchitecture::Maxwell;
344 }
345
346 if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
347 VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
348 conservative_raster_props.sType =
349 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
350 physical_properties.pNext = &conservative_raster_props;
351 physical.GetProperties2(physical_properties);
352 if (conservative_raster_props.degenerateLinesRasterized) {
353 return NvidiaArchitecture::Volta;
354 }
355 return NvidiaArchitecture::Pascal;
356 }
357 }
358
359 return NvidiaArchitecture::KeplerOrOlder;
323} 360}
324 361
325std::vector<const char*> ExtensionListForVulkan( 362std::vector<const char*> ExtensionListForVulkan(
@@ -420,7 +457,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
420 first_next = &diagnostics_nv; 457 first_next = &diagnostics_nv;
421 } 458 }
422 459
423 is_blit_depth_stencil_supported = TestDepthStencilBlits(); 460 is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT);
461 is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT);
424 is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); 462 is_optimal_astc_supported = ComputeIsOptimalAstcSupported();
425 is_warp_potentially_bigger = !extensions.subgroup_size_control || 463 is_warp_potentially_bigger = !extensions.subgroup_size_control ||
426 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; 464 properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
@@ -495,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
495 if (is_nvidia) { 533 if (is_nvidia) {
496 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 534 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
497 const auto arch = GetNvidiaArchitecture(physical, supported_extensions); 535 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
498 switch (arch) { 536 if (arch >= NvidiaArchitecture::AmpereOrNewer) {
499 case NvidiaArchitecture::AmpereOrNewer:
500 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); 537 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
501 features.shader_float16_int8.shaderFloat16 = false; 538 features.shader_float16_int8.shaderFloat16 = false;
502 break; 539 } else if (arch <= NvidiaArchitecture::Volta) {
503 case NvidiaArchitecture::Turing:
504 break;
505 case NvidiaArchitecture::VoltaOrOlder:
506 if (nv_major_version < 527) { 540 if (nv_major_version < 527) {
507 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 541 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
508 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 542 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
509 } 543 }
510 break;
511 } 544 }
512 if (nv_major_version >= 510) { 545 if (nv_major_version >= 510) {
513 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); 546 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
@@ -652,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
652 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); 685 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
653 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 686 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
654 } 687 }
688 } else if (extensions.push_descriptor && is_nvidia) {
689 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
690 if (arch <= NvidiaArchitecture::Pascal) {
691 LOG_WARNING(Render_Vulkan,
692 "Pascal and older architectures have broken VK_KHR_push_descriptor");
693 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
694 }
655 } 695 }
696
656 if (is_mvk) { 697 if (is_mvk) {
657 LOG_WARNING(Render_Vulkan, 698 LOG_WARNING(Render_Vulkan,
658 "MVK driver breaks when using more than 16 vertex attributes/bindings"); 699 "MVK driver breaks when using more than 16 vertex attributes/bindings");
@@ -774,14 +815,13 @@ bool Device::ComputeIsOptimalAstcSupported() const {
774 return true; 815 return true;
775} 816}
776 817
777bool Device::TestDepthStencilBlits() const { 818bool Device::TestDepthStencilBlits(VkFormat format) const {
778 static constexpr VkFormatFeatureFlags required_features = 819 static constexpr VkFormatFeatureFlags required_features =
779 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; 820 VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
780 const auto test_features = [](VkFormatProperties props) { 821 const auto test_features = [](VkFormatProperties props) {
781 return (props.optimalTilingFeatures & required_features) == required_features; 822 return (props.optimalTilingFeatures & required_features) == required_features;
782 }; 823 };
783 return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) && 824 return test_features(format_properties.at(format));
784 test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT));
785} 825}
786 826
787bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, 827bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
@@ -1051,6 +1091,13 @@ void Device::RemoveUnsuitableExtensions() {
1051 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, 1091 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
1052 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 1092 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
1053 1093
1094 // VK_EXT_depth_bias_control
1095 extensions.depth_bias_control =
1096 features.depth_bias_control.depthBiasControl &&
1097 features.depth_bias_control.leastRepresentableValueForceUnormRepresentation;
1098 RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control,
1099 VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME);
1100
1054 // VK_EXT_depth_clip_control 1101 // VK_EXT_depth_clip_control
1055 extensions.depth_clip_control = features.depth_clip_control.depthClipControl; 1102 extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
1056 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, 1103 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..282a2925d 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) \
@@ -304,7 +314,7 @@ public:
304 return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY; 314 return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
305 } 315 }
306 316
307 /// Returns true if the device suppors float64 natively. 317 /// Returns true if the device supports float64 natively.
308 bool IsFloat64Supported() const { 318 bool IsFloat64Supported() const {
309 return features.features.shaderFloat64; 319 return features.features.shaderFloat64;
310 } 320 }
@@ -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_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 3ef381a38..82767fdf0 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -9,6 +9,7 @@
9#include "common/alignment.h" 9#include "common/alignment.h"
10#include "common/assert.h" 10#include "common/assert.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/literals.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/polyfill_ranges.h" 14#include "common/polyfill_ranges.h"
14#include "video_core/vulkan_common/vma.h" 15#include "video_core/vulkan_common/vma.h"
@@ -69,8 +70,7 @@ struct Range {
69 case MemoryUsage::Download: 70 case MemoryUsage::Download:
70 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; 71 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
71 case MemoryUsage::DeviceLocal: 72 case MemoryUsage::DeviceLocal:
72 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | 73 return {};
73 VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
74 } 74 }
75 return {}; 75 return {};
76} 76}
@@ -212,7 +212,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
212 : device{device_}, allocator{device.GetAllocator()}, 212 : device{device_}, allocator{device.GetAllocator()},
213 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, 213 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
214 buffer_image_granularity{ 214 buffer_image_granularity{
215 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} 215 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
216 // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
217 // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
218 // the heap running out of memory. With RenderDoc attached and only a small host/device region,
219 // only allow the stream buffer in this memory heap.
220 if (device.HasDebuggingToolAttached()) {
221 using namespace Common::Literals;
222 ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
223 if (heap.size <= 256_MiB) {
224 valid_memory_types &= ~(1u << index);
225 }
226 });
227 }
228}
216 229
217MemoryAllocator::~MemoryAllocator() = default; 230MemoryAllocator::~MemoryAllocator() = default;
218 231
@@ -244,7 +257,7 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa
244 .usage = MemoryUsageVma(usage), 257 .usage = MemoryUsageVma(usage),
245 .requiredFlags = 0, 258 .requiredFlags = 0,
246 .preferredFlags = MemoryUsagePreferedVmaFlags(usage), 259 .preferredFlags = MemoryUsagePreferedVmaFlags(usage),
247 .memoryTypeBits = 0, 260 .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
248 .pool = VK_NULL_HANDLE, 261 .pool = VK_NULL_HANDLE,
249 .pUserData = nullptr, 262 .pUserData = nullptr,
250 .priority = 0.f, 263 .priority = 0.f,
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index f449bc8d0..38a182bcb 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -7,6 +7,7 @@
7#include <span> 7#include <span>
8#include <vector> 8#include <vector>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/vulkan_common/vulkan_device.h"
10#include "video_core/vulkan_common/vulkan_wrapper.h" 11#include "video_core/vulkan_common/vulkan_wrapper.h"
11 12
12VK_DEFINE_HANDLE(VmaAllocator) 13VK_DEFINE_HANDLE(VmaAllocator)
@@ -26,6 +27,18 @@ enum class MemoryUsage {
26 Stream, ///< Requests device local host visible buffer, falling back host memory. 27 Stream, ///< Requests device local host visible buffer, falling back host memory.
27}; 28};
28 29
30template <typename F>
31void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
32 auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
33 for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
34 auto& memory_type = memory_props.memoryTypes[i];
35 if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
36 (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
37 f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
38 }
39 }
40}
41
29/// Ownership handle of a memory commitment. 42/// Ownership handle of a memory commitment.
30/// Points to a subregion of a memory allocation. 43/// Points to a subregion of a memory allocation.
31class MemoryCommit { 44class MemoryCommit {
@@ -124,6 +137,7 @@ private:
124 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. 137 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
125 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers 138 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
126 // and optimal images 139 // and optimal images
140 u32 valid_memory_types{~0u};
127}; 141};
128 142
129} // namespace Vulkan 143} // namespace Vulkan
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..0487cd3b6 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -117,6 +117,9 @@ public:
117 virtual ~Exception() = default; 117 virtual ~Exception() = default;
118 118
119 const char* what() const noexcept override; 119 const char* what() const noexcept override;
120 VkResult GetResult() const noexcept {
121 return result;
122 }
120 123
121private: 124private:
122 VkResult result; 125 VkResult result;
@@ -185,6 +188,7 @@ struct DeviceDispatch : InstanceDispatch {
185 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; 188 PFN_vkBeginCommandBuffer vkBeginCommandBuffer{};
186 PFN_vkBindBufferMemory vkBindBufferMemory{}; 189 PFN_vkBindBufferMemory vkBindBufferMemory{};
187 PFN_vkBindImageMemory vkBindImageMemory{}; 190 PFN_vkBindImageMemory vkBindImageMemory{};
191 PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{};
188 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; 192 PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{};
189 PFN_vkCmdBeginQuery vkCmdBeginQuery{}; 193 PFN_vkCmdBeginQuery vkCmdBeginQuery{};
190 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; 194 PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{};
@@ -202,6 +206,7 @@ struct DeviceDispatch : InstanceDispatch {
202 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; 206 PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{};
203 PFN_vkCmdCopyImage vkCmdCopyImage{}; 207 PFN_vkCmdCopyImage vkCmdCopyImage{};
204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; 208 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
209 PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults{};
205 PFN_vkCmdDispatch vkCmdDispatch{}; 210 PFN_vkCmdDispatch vkCmdDispatch{};
206 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; 211 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
207 PFN_vkCmdDraw vkCmdDraw{}; 212 PFN_vkCmdDraw vkCmdDraw{};
@@ -210,6 +215,8 @@ struct DeviceDispatch : InstanceDispatch {
210 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{}; 215 PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{};
211 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; 216 PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
212 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; 217 PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
218 PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
219 PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
213 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; 220 PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
214 PFN_vkCmdEndQuery vkCmdEndQuery{}; 221 PFN_vkCmdEndQuery vkCmdEndQuery{};
215 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; 222 PFN_vkCmdEndRenderPass vkCmdEndRenderPass{};
@@ -222,6 +229,7 @@ struct DeviceDispatch : InstanceDispatch {
222 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; 229 PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
223 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; 230 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
224 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; 231 PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
232 PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{};
225 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; 233 PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{};
226 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; 234 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{};
227 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; 235 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{};
@@ -1182,6 +1190,13 @@ public:
1182 count_offset, draw_count, stride); 1190 count_offset, draw_count, stride);
1183 } 1191 }
1184 1192
1193 void DrawIndirectByteCountEXT(u32 instance_count, u32 first_instance, VkBuffer counter_buffer,
1194 VkDeviceSize counter_buffer_offset, u32 counter_offset,
1195 u32 stride) {
1196 dld->vkCmdDrawIndirectByteCountEXT(handle, instance_count, first_instance, counter_buffer,
1197 counter_buffer_offset, counter_offset, stride);
1198 }
1199
1185 void ClearAttachments(Span<VkClearAttachment> attachments, 1200 void ClearAttachments(Span<VkClearAttachment> attachments,
1186 Span<VkClearRect> rects) const noexcept { 1201 Span<VkClearRect> rects) const noexcept {
1187 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), 1202 dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
@@ -1270,6 +1285,13 @@ public:
1270 regions.data()); 1285 regions.data());
1271 } 1286 }
1272 1287
1288 void CopyQueryPoolResults(VkQueryPool query_pool, u32 first_query, u32 query_count,
1289 VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize stride,
1290 VkQueryResultFlags flags) const noexcept {
1291 dld->vkCmdCopyQueryPoolResults(handle, query_pool, first_query, query_count, dst_buffer,
1292 dst_offset, stride, flags);
1293 }
1294
1273 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, 1295 void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
1274 u32 data) const noexcept { 1296 u32 data) const noexcept {
1275 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); 1297 dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
@@ -1315,6 +1337,18 @@ public:
1315 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); 1337 dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
1316 } 1338 }
1317 1339
1340 void SetDepthBias(float constant_factor, float clamp, float slope_factor,
1341 VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
1342 VkDepthBiasInfoEXT info{
1343 .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT,
1344 .pNext = extra,
1345 .depthBiasConstantFactor = constant_factor,
1346 .depthBiasClamp = clamp,
1347 .depthBiasSlopeFactor = slope_factor,
1348 };
1349 dld->vkCmdSetDepthBias2EXT(handle, &info);
1350 }
1351
1318 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { 1352 void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
1319 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); 1353 dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
1320 } 1354 }
@@ -1448,6 +1482,15 @@ public:
1448 counter_buffers, counter_buffer_offsets); 1482 counter_buffers, counter_buffer_offsets);
1449 } 1483 }
1450 1484
1485 void BeginConditionalRenderingEXT(
1486 const VkConditionalRenderingBeginInfoEXT& info) const noexcept {
1487 dld->vkCmdBeginConditionalRenderingEXT(handle, &info);
1488 }
1489
1490 void EndConditionalRenderingEXT() const noexcept {
1491 dld->vkCmdEndConditionalRenderingEXT(handle);
1492 }
1493
1451 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { 1494 void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept {
1452 const VkDebugUtilsLabelEXT label_info{ 1495 const VkDebugUtilsLabelEXT label_info{
1453 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, 1496 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 8f86a1553..9ebece907 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -195,6 +195,8 @@ add_executable(yuzu
195 multiplayer/state.cpp 195 multiplayer/state.cpp
196 multiplayer/state.h 196 multiplayer/state.h
197 multiplayer/validation.h 197 multiplayer/validation.h
198 play_time_manager.cpp
199 play_time_manager.h
198 precompiled_headers.h 200 precompiled_headers.h
199 qt_common.cpp 201 qt_common.cpp
200 qt_common.h 202 qt_common.h
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 9ccfb2435..81dd51ad3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
42 for (auto* setting : Settings::values.linkage.by_category[category]) { 42 for (auto* setting : Settings::values.linkage.by_category[category]) {
43 settings.push_back(setting); 43 settings.push_back(setting);
44 } 44 }
45 for (auto* setting : UISettings::values.linkage.by_category[category]) {
46 settings.push_back(setting);
47 }
45 }; 48 };
46 49
47 push(Settings::Category::Audio); 50 push(Settings::Category::Audio);
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index a9fde9f4f..82f3b6e78 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
126 connect(ui->show_play_time, &QCheckBox::stateChanged, this,
127 &ConfigureUi::RequestGameListUpdate);
126 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 128 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
127 &ConfigureUi::RequestGameListUpdate); 129 &ConfigureUi::RequestGameListUpdate);
128 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), 130 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
167 UISettings::values.show_compat = ui->show_compat->isChecked(); 169 UISettings::values.show_compat = ui->show_compat->isChecked();
168 UISettings::values.show_size = ui->show_size->isChecked(); 170 UISettings::values.show_size = ui->show_size->isChecked();
169 UISettings::values.show_types = ui->show_types->isChecked(); 171 UISettings::values.show_types = ui->show_types->isChecked();
172 UISettings::values.show_play_time = ui->show_play_time->isChecked();
170 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); 173 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
171 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); 174 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
172 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 175 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
179 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); 182 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
180 UISettings::values.screenshot_height.SetValue(height); 183 UISettings::values.screenshot_height.SetValue(height);
181 184
185 RequestGameListUpdate();
182 system.ApplySettings(); 186 system.ApplySettings();
183} 187}
184 188
@@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
194 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); 198 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
195 ui->show_size->setChecked(UISettings::values.show_size.GetValue()); 199 ui->show_size->setChecked(UISettings::values.show_size.GetValue());
196 ui->show_types->setChecked(UISettings::values.show_types.GetValue()); 200 ui->show_types->setChecked(UISettings::values.show_types.GetValue());
201 ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
197 ui->game_icon_size_combobox->setCurrentIndex( 202 ui->game_icon_size_combobox->setCurrentIndex(
198 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); 203 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
199 ui->folder_icon_size_combobox->setCurrentIndex( 204 ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index cb66ef104..b8e648381 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -105,6 +105,13 @@
105 </widget> 105 </widget>
106 </item> 106 </item>
107 <item> 107 <item>
108 <widget class="QCheckBox" name="show_play_time">
109 <property name="text">
110 <string>Show Play Time Column</string>
111 </property>
112 </widget>
113 </item>
114 <item>
108 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> 115 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
109 <item> 116 <item>
110 <widget class="QLabel" name="game_icon_size_label"> 117 <widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 276bdbaba..a4e8af1b4 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
29 INSERT(Settings, sink_id, "Output Engine:", ""); 29 INSERT(Settings, sink_id, "Output Engine:", "");
30 INSERT(Settings, audio_output_device_id, "Output Device:", ""); 30 INSERT(Settings, audio_output_device_id, "Output Device:", "");
31 INSERT(Settings, audio_input_device_id, "Input Device:", ""); 31 INSERT(Settings, audio_input_device_id, "Input Device:", "");
32 INSERT(Settings, audio_muted, "Mute audio when in background", ""); 32 INSERT(Settings, audio_muted, "Mute audio", "");
33 INSERT(Settings, volume, "Volume:", ""); 33 INSERT(Settings, volume, "Volume:", "");
34 INSERT(Settings, dump_audio_commands, "", ""); 34 INSERT(Settings, dump_audio_commands, "", "");
35 INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
35 36
36 // Core 37 // Core
37 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); 38 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..74f48031a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
312} 312}
313 313
314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, 314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
315 Core::System& system_, GMainWindow* parent) 315 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
316 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { 316 GMainWindow* parent)
317 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
318 play_time_manager{play_time_manager_}, system{system_} {
317 watcher = new QFileSystemWatcher(this); 319 watcher = new QFileSystemWatcher(this);
318 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); 320 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
319 321
@@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
340 342
341 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); 343 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
342 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 344 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
345 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
343 item_model->setSortRole(GameListItemPath::SortRole); 346 item_model->setSortRole(GameListItemPath::SortRole);
344 347
345 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); 348 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
548 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); 551 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
549 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); 552 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
550 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 553 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
554 QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
551 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); 555 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
552 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); 556 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
553 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); 557 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -560,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 564 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 565 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 566 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
563#ifndef WIN32
564 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 567 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
565 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 568 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
569#ifndef WIN32
566 QAction* create_applications_menu_shortcut = 570 QAction* create_applications_menu_shortcut =
567 shortcut_menu->addAction(tr("Add to Applications Menu")); 571 shortcut_menu->addAction(tr("Add to Applications Menu"));
568#endif 572#endif
@@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
622 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 626 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
623 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 627 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
624 }); 628 });
629 connect(remove_play_time_data, &QAction::triggered,
630 [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
625 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { 631 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
626 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); 632 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
627 }); 633 });
@@ -638,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 644 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 645 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
640 }); 646 });
641#ifndef WIN32
642 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 647 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
643 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 648 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
644 }); 649 });
650#ifndef WIN32
645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 651 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 652 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
647 }); 653 });
@@ -790,6 +796,7 @@ void GameList::RetranslateUI() {
790 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); 796 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
791 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); 797 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
792 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); 798 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
799 item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
793} 800}
794 801
795void GameListSearchField::changeEvent(QEvent* event) { 802void GameListSearchField::changeEvent(QEvent* event) {
@@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
817 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 824 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
818 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); 825 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
819 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); 826 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
827 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
820 828
821 // Delete any rows that might already exist if we're repopulating 829 // Delete any rows that might already exist if we're repopulating
822 item_model->removeRows(0, item_model->rowCount()); 830 item_model->removeRows(0, item_model->rowCount());
@@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
825 emit ShouldCancelWorker(); 833 emit ShouldCancelWorker();
826 834
827 GameListWorker* worker = 835 GameListWorker* worker =
828 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 836 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
829 837
830 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 838 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
831 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, 839 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 1fcbbf0ba..712570cea 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
18#include "core/core.h" 18#include "core/core.h"
19#include "uisettings.h" 19#include "uisettings.h"
20#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
21#include "yuzu/play_time_manager.h"
21 22
22namespace Core { 23namespace Core {
23class System; 24class System;
@@ -75,11 +76,13 @@ public:
75 COLUMN_ADD_ONS, 76 COLUMN_ADD_ONS,
76 COLUMN_FILE_TYPE, 77 COLUMN_FILE_TYPE,
77 COLUMN_SIZE, 78 COLUMN_SIZE,
79 COLUMN_PLAY_TIME,
78 COLUMN_COUNT, // Number of columns 80 COLUMN_COUNT, // Number of columns
79 }; 81 };
80 82
81 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 83 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
82 FileSys::ManualContentProvider* provider_, Core::System& system_, 84 FileSys::ManualContentProvider* provider_,
85 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
83 GMainWindow* parent = nullptr); 86 GMainWindow* parent = nullptr);
84 ~GameList() override; 87 ~GameList() override;
85 88
@@ -113,6 +116,7 @@ signals:
113 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 116 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
114 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 117 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
115 const std::string& game_path); 118 const std::string& game_path);
119 void RemovePlayTimeRequested(u64 program_id);
116 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 120 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
117 void VerifyIntegrityRequested(const std::string& game_path); 121 void VerifyIntegrityRequested(const std::string& game_path);
118 void CopyTIDRequested(u64 program_id); 122 void CopyTIDRequested(u64 program_id);
@@ -168,6 +172,7 @@ private:
168 172
169 friend class GameListSearchField; 173 friend class GameListSearchField;
170 174
175 const PlayTime::PlayTimeManager& play_time_manager;
171 Core::System& system; 176 Core::System& system;
172}; 177};
173 178
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1800f090f..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,6 +18,7 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/logging/log.h" 19#include "common/logging/log.h"
20#include "common/string_util.h" 20#include "common/string_util.h"
21#include "yuzu/play_time_manager.h"
21#include "yuzu/uisettings.h" 22#include "yuzu/uisettings.h"
22#include "yuzu/util/util.h" 23#include "yuzu/util/util.h"
23 24
@@ -221,6 +222,31 @@ public:
221 } 222 }
222}; 223};
223 224
225/**
226 * GameListItem for Play Time values.
227 * This object stores the play time of a game in seconds, and its readable
228 * representation in minutes/hours
229 */
230class GameListItemPlayTime : public GameListItem {
231public:
232 static constexpr int PlayTimeRole = SortRole;
233
234 GameListItemPlayTime() = default;
235 explicit GameListItemPlayTime(const qulonglong time_seconds) {
236 setData(time_seconds, PlayTimeRole);
237 }
238
239 void setData(const QVariant& value, int role) override {
240 qulonglong time_seconds = value.toULongLong();
241 GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
242 GameListItem::setData(value, PlayTimeRole);
243 }
244
245 bool operator<(const QStandardItem& other) const override {
246 return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
247 }
248};
249
224class GameListDir : public GameListItem { 250class GameListDir : public GameListItem {
225public: 251public:
226 static constexpr int GameDirRole = Qt::UserRole + 2; 252 static constexpr int GameDirRole = Qt::UserRole + 2;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index e7fb8a282..588f1dd6e 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
194 const std::size_t size, const std::vector<u8>& icon, 194 const std::size_t size, const std::vector<u8>& icon,
195 Loader::AppLoader& loader, u64 program_id, 195 Loader::AppLoader& loader, u64 program_id,
196 const CompatibilityList& compatibility_list, 196 const CompatibilityList& compatibility_list,
197 const PlayTime::PlayTimeManager& play_time_manager,
197 const FileSys::PatchManager& patch) { 198 const FileSys::PatchManager& patch) {
198 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 199 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
199 200
@@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
212 new GameListItemCompat(compatibility), 213 new GameListItemCompat(compatibility),
213 new GameListItem(file_type_string), 214 new GameListItem(file_type_string),
214 new GameListItemSize(size), 215 new GameListItemSize(size),
216 new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
215 }; 217 };
216 218
217 const auto patch_versions = GetGameListCachedObject( 219 const auto patch_versions = GetGameListCachedObject(
@@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
227GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, 229GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
228 FileSys::ManualContentProvider* provider_, 230 FileSys::ManualContentProvider* provider_,
229 QVector<UISettings::GameDir>& game_dirs_, 231 QVector<UISettings::GameDir>& game_dirs_,
230 const CompatibilityList& compatibility_list_, Core::System& system_) 232 const CompatibilityList& compatibility_list_,
233 const PlayTime::PlayTimeManager& play_time_manager_,
234 Core::System& system_)
231 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, 235 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
232 compatibility_list{compatibility_list_}, system{system_} {} 236 compatibility_list{compatibility_list_},
237 play_time_manager{play_time_manager_}, system{system_} {}
233 238
234GameListWorker::~GameListWorker() = default; 239GameListWorker::~GameListWorker() = default;
235 240
@@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
280 } 285 }
281 286
282 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, 287 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
283 program_id, compatibility_list, patch), 288 program_id, compatibility_list, play_time_manager, patch),
284 parent_dir); 289 parent_dir);
285 } 290 }
286} 291}
@@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
357 362
358 emit EntryReady(MakeGameListEntry(physical_name, name, 363 emit EntryReady(MakeGameListEntry(physical_name, name,
359 Common::FS::GetSize(physical_name), icon, 364 Common::FS::GetSize(physical_name), icon,
360 *loader, id, compatibility_list, patch), 365 *loader, id, compatibility_list,
366 play_time_manager, patch),
361 parent_dir); 367 parent_dir);
362 } 368 }
363 } else { 369 } else {
@@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
370 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 376 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
371 system.GetContentProvider()}; 377 system.GetContentProvider()};
372 378
373 emit EntryReady( 379 emit EntryReady(MakeGameListEntry(physical_name, name,
374 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), 380 Common::FS::GetSize(physical_name), icon,
375 icon, *loader, program_id, compatibility_list, patch), 381 *loader, program_id, compatibility_list,
376 parent_dir); 382 play_time_manager, patch),
383 parent_dir);
377 } 384 }
378 } 385 }
379 } else if (is_dir) { 386 } else if (is_dir) {
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..2bb0a0cb6 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -13,6 +13,7 @@
13#include <QString> 13#include <QString>
14 14
15#include "yuzu/compatibility_list.h" 15#include "yuzu/compatibility_list.h"
16#include "yuzu/play_time_manager.h"
16 17
17namespace Core { 18namespace Core {
18class System; 19class System;
@@ -36,7 +37,9 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 37 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 38 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 39 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 40 const CompatibilityList& compatibility_list_,
41 const PlayTime::PlayTimeManager& play_time_manager_,
42 Core::System& system_);
40 ~GameListWorker() override; 43 ~GameListWorker() override;
41 44
42 /// Starts the processing of directory tree information. 45 /// Starts the processing of directory tree information.
@@ -76,6 +79,7 @@ private:
76 FileSys::ManualContentProvider* provider; 79 FileSys::ManualContentProvider* provider;
77 QVector<UISettings::GameDir>& game_dirs; 80 QVector<UISettings::GameDir>& game_dirs;
78 const CompatibilityList& compatibility_list; 81 const CompatibilityList& compatibility_list;
82 const PlayTime::PlayTimeManager& play_time_manager;
79 83
80 QStringList watch_list; 84 QStringList watch_list;
81 std::atomic_bool stop_processing; 85 std::atomic_bool stop_processing;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index d32aa9615..5427758c1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -18,6 +18,8 @@
18#include <sys/socket.h> 18#include <sys/socket.h>
19#endif 19#endif
20 20
21#include <boost/container/flat_set.hpp>
22
21// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 23// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
22#include "applets/qt_amiibo_settings.h" 24#include "applets/qt_amiibo_settings.h"
23#include "applets/qt_controller.h" 25#include "applets/qt_controller.h"
@@ -96,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
96#include "common/scm_rev.h" 98#include "common/scm_rev.h"
97#include "common/scope_exit.h" 99#include "common/scope_exit.h"
98#ifdef _WIN32 100#ifdef _WIN32
101#include <shlobj.h>
99#include "common/windows/timer_resolution.h" 102#include "common/windows/timer_resolution.h"
100#endif 103#endif
101#ifdef ARCHITECTURE_x86_64 104#ifdef ARCHITECTURE_x86_64
@@ -148,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
148#include "yuzu/install_dialog.h" 151#include "yuzu/install_dialog.h"
149#include "yuzu/loading_screen.h" 152#include "yuzu/loading_screen.h"
150#include "yuzu/main.h" 153#include "yuzu/main.h"
154#include "yuzu/play_time_manager.h"
151#include "yuzu/startup_checks.h" 155#include "yuzu/startup_checks.h"
152#include "yuzu/uisettings.h" 156#include "yuzu/uisettings.h"
153#include "yuzu/util/clickable_label.h" 157#include "yuzu/util/clickable_label.h"
@@ -336,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
336 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 340 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
337 discord_rpc->Update(); 341 discord_rpc->Update();
338 342
343 play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
344
339 system->GetRoomNetwork().Init(); 345 system->GetRoomNetwork().Init();
340 346
341 RegisterMetaTypes(); 347 RegisterMetaTypes();
@@ -984,7 +990,7 @@ void GMainWindow::InitializeWidgets() {
984 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); 990 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
985 render_window->hide(); 991 render_window->hide();
986 992
987 game_list = new GameList(vfs, provider.get(), *system, this); 993 game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
988 ui->horizontalLayout->addWidget(game_list); 994 ui->horizontalLayout->addWidget(game_list);
989 995
990 game_list_placeholder = new GameListPlaceholder(this); 996 game_list_placeholder = new GameListPlaceholder(this);
@@ -1445,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
1445 Settings::values.audio_muted = false; 1451 Settings::values.audio_muted = false;
1446 auto_muted = false; 1452 auto_muted = false;
1447 } 1453 }
1454 UpdateVolumeUI();
1448 } 1455 }
1449} 1456}
1450 1457
@@ -1458,6 +1465,8 @@ void GMainWindow::ConnectWidgetEvents() {
1458 connect(game_list, &GameList::RemoveInstalledEntryRequested, this, 1465 connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
1459 &GMainWindow::OnGameListRemoveInstalledEntry); 1466 &GMainWindow::OnGameListRemoveInstalledEntry);
1460 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); 1467 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
1468 connect(game_list, &GameList::RemovePlayTimeRequested, this,
1469 &GMainWindow::OnGameListRemovePlayTimeData);
1461 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 1470 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
1462 connect(game_list, &GameList::VerifyIntegrityRequested, this, 1471 connect(game_list, &GameList::VerifyIntegrityRequested, this,
1463 &GMainWindow::OnGameListVerifyIntegrity); 1472 &GMainWindow::OnGameListVerifyIntegrity);
@@ -1551,6 +1560,16 @@ void GMainWindow::ConnectMenuEvents() {
1551 // Tools 1560 // Tools
1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1561 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1553 ReinitializeKeyBehavior::Warning)); 1562 ReinitializeKeyBehavior::Warning));
1563 connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
1564 connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
1565 [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
1566 connect_menu(ui->action_Load_Cabinet_Eraser,
1567 [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
1568 connect_menu(ui->action_Load_Cabinet_Restorer,
1569 [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
1570 connect_menu(ui->action_Load_Cabinet_Formatter,
1571 [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
1572 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1554 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1573 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1555 1574
1556 // TAS 1575 // TAS
@@ -1567,6 +1586,7 @@ void GMainWindow::ConnectMenuEvents() {
1567 1586
1568void GMainWindow::UpdateMenuState() { 1587void GMainWindow::UpdateMenuState() {
1569 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); 1588 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
1589 const bool is_firmware_available = CheckFirmwarePresence();
1570 1590
1571 const std::array running_actions{ 1591 const std::array running_actions{
1572 ui->action_Stop, 1592 ui->action_Stop,
@@ -1577,10 +1597,23 @@ void GMainWindow::UpdateMenuState() {
1577 ui->action_Pause, 1597 ui->action_Pause,
1578 }; 1598 };
1579 1599
1600 const std::array applet_actions{
1601 ui->action_Load_Album,
1602 ui->action_Load_Cabinet_Nickname_Owner,
1603 ui->action_Load_Cabinet_Eraser,
1604 ui->action_Load_Cabinet_Restorer,
1605 ui->action_Load_Cabinet_Formatter,
1606 ui->action_Load_Mii_Edit,
1607 };
1608
1580 for (QAction* action : running_actions) { 1609 for (QAction* action : running_actions) {
1581 action->setEnabled(emulation_running); 1610 action->setEnabled(emulation_running);
1582 } 1611 }
1583 1612
1613 for (QAction* action : applet_actions) {
1614 action->setEnabled(is_firmware_available && !emulation_running);
1615 }
1616
1584 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); 1617 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
1585 1618
1586 if (emulation_running && is_paused) { 1619 if (emulation_running && is_paused) {
@@ -2100,6 +2133,8 @@ void GMainWindow::OnEmulationStopped() {
2100 OnTasStateChanged(); 2133 OnTasStateChanged();
2101 render_window->FinalizeCamera(); 2134 render_window->FinalizeCamera();
2102 2135
2136 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
2137
2103 // Enable all controllers 2138 // Enable all controllers
2104 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); 2139 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
2105 2140
@@ -2508,6 +2543,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2508 } 2543 }
2509} 2544}
2510 2545
2546void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
2547 if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
2548 QMessageBox::Yes | QMessageBox::No,
2549 QMessageBox::No) != QMessageBox::Yes) {
2550 return;
2551 }
2552
2553 play_time_manager->ResetProgramPlayTime(program_id);
2554 game_list->PopulateAsync(UISettings::values.game_dirs);
2555}
2556
2511void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { 2557void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
2512 const auto target_file_name = [target] { 2558 const auto target_file_name = [target] {
2513 switch (target) { 2559 switch (target) {
@@ -2799,7 +2845,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2799 const QStringList args = QApplication::arguments(); 2845 const QStringList args = QApplication::arguments();
2800 std::filesystem::path yuzu_command = args[0].toStdString(); 2846 std::filesystem::path yuzu_command = args[0].toStdString();
2801 2847
2802#if defined(__linux__) || defined(__FreeBSD__)
2803 // If relative path, make it an absolute path 2848 // If relative path, make it an absolute path
2804 if (yuzu_command.c_str()[0] == '.') { 2849 if (yuzu_command.c_str()[0] == '.') {
2805 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2850 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2822,12 +2867,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2822 UISettings::values.shortcut_already_warned = true; 2867 UISettings::values.shortcut_already_warned = true;
2823 } 2868 }
2824#endif // __linux__ 2869#endif // __linux__
2825#endif // __linux__ || __FreeBSD__
2826 2870
2827 std::filesystem::path target_directory{}; 2871 std::filesystem::path target_directory{};
2828 // Determine target directory for shortcut 2872 // Determine target directory for shortcut
2829#if defined(__linux__) || defined(__FreeBSD__) 2873#if defined(WIN32)
2874 const char* home = std::getenv("USERPROFILE");
2875#else
2830 const char* home = std::getenv("HOME"); 2876 const char* home = std::getenv("HOME");
2877#endif
2831 const std::filesystem::path home_path = (home == nullptr ? "~" : home); 2878 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2832 const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); 2879 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2833 2880
@@ -2837,7 +2884,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2837 QMessageBox::critical( 2884 QMessageBox::critical(
2838 this, tr("Create Shortcut"), 2885 this, tr("Create Shortcut"),
2839 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2886 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
2840 .arg(QString::fromStdString(target_directory)), 2887 .arg(QString::fromStdString(target_directory.generic_string())),
2841 QMessageBox::StandardButton::Ok); 2888 QMessageBox::StandardButton::Ok);
2842 return; 2889 return;
2843 } 2890 }
@@ -2845,15 +2892,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2845 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2892 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
2846 "applications"; 2893 "applications";
2847 if (!Common::FS::CreateDirs(target_directory)) { 2894 if (!Common::FS::CreateDirs(target_directory)) {
2848 QMessageBox::critical(this, tr("Create Shortcut"), 2895 QMessageBox::critical(
2849 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2896 this, tr("Create Shortcut"),
2850 "does not exist and cannot be created.") 2897 tr("Cannot create shortcut in applications menu. Path \"%1\" "
2851 .arg(QString::fromStdString(target_directory)), 2898 "does not exist and cannot be created.")
2852 QMessageBox::StandardButton::Ok); 2899 .arg(QString::fromStdString(target_directory.generic_string())),
2900 QMessageBox::StandardButton::Ok);
2853 return; 2901 return;
2854 } 2902 }
2855 } 2903 }
2856#endif
2857 2904
2858 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2905 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2859 // Determine full paths for icon and shortcut 2906 // Determine full paths for icon and shortcut
@@ -2875,9 +2922,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2875 const std::filesystem::path shortcut_path = 2922 const std::filesystem::path shortcut_path =
2876 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2923 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2877 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2924 : fmt::format("yuzu-{:016X}.desktop", program_id));
2925#elif defined(WIN32)
2926 std::filesystem::path icons_path =
2927 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2928 std::filesystem::path icon_path =
2929 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2930 : fmt::format("yuzu-{:016X}.ico", program_id)));
2878#else 2931#else
2879 const std::filesystem::path icon_path{}; 2932 std::string icon_extension;
2880 const std::filesystem::path shortcut_path{};
2881#endif 2933#endif
2882 2934
2883 // Get title from game file 2935 // Get title from game file
@@ -2902,29 +2954,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2902 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2954 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2903 } 2955 }
2904 2956
2905 QImage icon_jpeg = 2957 QImage icon_data =
2906 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2958 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2907#if defined(__linux__) || defined(__FreeBSD__) 2959#if defined(__linux__) || defined(__FreeBSD__)
2908 // Convert and write the icon as a PNG 2960 // Convert and write the icon as a PNG
2909 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2961 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2910 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2962 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2911 } else { 2963 } else {
2912 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2964 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2913 } 2965 }
2966#elif defined(WIN32)
2967 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2968 LOG_ERROR(Frontend, "Could not write icon to file");
2969 return;
2970 }
2914#endif // __linux__ 2971#endif // __linux__
2915 2972
2916#if defined(__linux__) || defined(__FreeBSD__) 2973#ifdef _WIN32
2974 // Replace characters that are illegal in Windows filenames by a dash
2975 const std::string illegal_chars = "<>:\"/\\|?*";
2976 for (char c : illegal_chars) {
2977 std::replace(title.begin(), title.end(), c, '_');
2978 }
2979 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2980#endif
2981
2917 const std::string comment = 2982 const std::string comment =
2918 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2983 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2919 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2984 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2920 const std::string categories = "Game;Emulator;Qt;"; 2985 const std::string categories = "Game;Emulator;Qt;";
2921 const std::string keywords = "Switch;Nintendo;"; 2986 const std::string keywords = "Switch;Nintendo;";
2922#else 2987
2923 const std::string comment{};
2924 const std::string arguments{};
2925 const std::string categories{};
2926 const std::string keywords{};
2927#endif
2928 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 2988 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2929 yuzu_command.string(), arguments, categories, keywords)) { 2989 yuzu_command.string(), arguments, categories, keywords)) {
2930 QMessageBox::critical(this, tr("Create Shortcut"), 2990 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3110,10 +3170,9 @@ void GMainWindow::OnMenuInstallToNAND() {
3110 QFuture<InstallResult> future; 3170 QFuture<InstallResult> future;
3111 InstallResult result; 3171 InstallResult result;
3112 3172
3113 if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || 3173 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3114 file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3115 3174
3116 future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); 3175 future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
3117 3176
3118 while (!future.isFinished()) { 3177 while (!future.isFinished()) {
3119 QCoreApplication::processEvents(); 3178 QCoreApplication::processEvents();
@@ -3172,7 +3231,7 @@ void GMainWindow::OnMenuInstallToNAND() {
3172 ui->action_Install_File_NAND->setEnabled(true); 3231 ui->action_Install_File_NAND->setEnabled(true);
3173} 3232}
3174 3233
3175InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { 3234InstallResult GMainWindow::InstallNSP(const QString& filename) {
3176 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, 3235 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3177 const FileSys::VirtualFile& dest, std::size_t block_size) { 3236 const FileSys::VirtualFile& dest, std::size_t block_size) {
3178 if (src == nullptr || dest == nullptr) { 3237 if (src == nullptr || dest == nullptr) {
@@ -3206,9 +3265,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3206 return InstallResult::Failure; 3265 return InstallResult::Failure;
3207 } 3266 }
3208 } else { 3267 } else {
3209 const auto xci = std::make_shared<FileSys::XCI>( 3268 return InstallResult::Failure;
3210 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3211 nsp = xci->GetSecurePartitionNSP();
3212 } 3269 }
3213 3270
3214 if (nsp->GetStatus() != Loader::ResultStatus::Success) { 3271 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -3334,6 +3391,9 @@ void GMainWindow::OnStartGame() {
3334 UpdateMenuState(); 3391 UpdateMenuState();
3335 OnTasStateChanged(); 3392 OnTasStateChanged();
3336 3393
3394 play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
3395 play_time_manager->Start();
3396
3337 discord_rpc->Update(); 3397 discord_rpc->Update();
3338} 3398}
3339 3399
@@ -3349,6 +3409,7 @@ void GMainWindow::OnRestartGame() {
3349 3409
3350void GMainWindow::OnPauseGame() { 3410void GMainWindow::OnPauseGame() {
3351 emu_thread->SetRunning(false); 3411 emu_thread->SetRunning(false);
3412 play_time_manager->Stop();
3352 UpdateMenuState(); 3413 UpdateMenuState();
3353 AllowOSSleep(); 3414 AllowOSSleep();
3354} 3415}
@@ -3369,6 +3430,9 @@ void GMainWindow::OnStopGame() {
3369 return; 3430 return;
3370 } 3431 }
3371 3432
3433 play_time_manager->Stop();
3434 // Update game list to show new play time
3435 game_list->PopulateAsync(UISettings::values.game_dirs);
3372 if (OnShutdownBegin()) { 3436 if (OnShutdownBegin()) {
3373 OnShutdownBeginDialog(); 3437 OnShutdownBeginDialog();
3374 } else { 3438 } else {
@@ -3942,6 +4006,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3942 shortcut_stream.close(); 4006 shortcut_stream.close();
3943 4007
3944 return true; 4008 return true;
4009#elif defined(WIN32)
4010 IShellLinkW* shell_link;
4011 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4012 (void**)&shell_link);
4013 if (FAILED(hres)) {
4014 return false;
4015 }
4016 shell_link->SetPath(
4017 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4018 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4019 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4020 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4021
4022 IPersistFile* persist_file;
4023 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4024 if (FAILED(hres)) {
4025 return false;
4026 }
4027
4028 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4029 if (FAILED(hres)) {
4030 return false;
4031 }
4032
4033 persist_file->Release();
4034 shell_link->Release();
4035
4036 return true;
3945#endif 4037#endif
3946 return false; 4038 return false;
3947} 4039}
@@ -4134,6 +4226,76 @@ void GMainWindow::OnToggleStatusBar() {
4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4226 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4135} 4227}
4136 4228
4229void GMainWindow::OnAlbum() {
4230 constexpr u64 AlbumId = 0x010000000000100Dull;
4231 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4232 if (!bis_system) {
4233 QMessageBox::warning(this, tr("No firmware available"),
4234 tr("Please install the firmware to use the Album applet."));
4235 return;
4236 }
4237
4238 auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
4239 if (!album_nca) {
4240 QMessageBox::warning(this, tr("Album Applet"),
4241 tr("Album applet is not available. Please reinstall firmware."));
4242 return;
4243 }
4244
4245 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
4246
4247 const auto filename = QString::fromStdString(album_nca->GetFullPath());
4248 UISettings::values.roms_path = QFileInfo(filename).path();
4249 BootGame(filename);
4250}
4251
4252void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
4253 constexpr u64 CabinetId = 0x0100000000001002ull;
4254 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4255 if (!bis_system) {
4256 QMessageBox::warning(this, tr("No firmware available"),
4257 tr("Please install the firmware to use the Cabinet applet."));
4258 return;
4259 }
4260
4261 auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
4262 if (!cabinet_nca) {
4263 QMessageBox::warning(this, tr("Cabinet Applet"),
4264 tr("Cabinet applet is not available. Please reinstall firmware."));
4265 return;
4266 }
4267
4268 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
4269 system->GetAppletManager().SetCabinetMode(mode);
4270
4271 const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
4272 UISettings::values.roms_path = QFileInfo(filename).path();
4273 BootGame(filename);
4274}
4275
4276void GMainWindow::OnMiiEdit() {
4277 constexpr u64 MiiEditId = 0x0100000000001009ull;
4278 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4279 if (!bis_system) {
4280 QMessageBox::warning(this, tr("No firmware available"),
4281 tr("Please install the firmware to use the Mii editor."));
4282 return;
4283 }
4284
4285 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4286 if (!mii_applet_nca) {
4287 QMessageBox::warning(this, tr("Mii Edit Applet"),
4288 tr("Mii editor is not available. Please reinstall firmware."));
4289 return;
4290 }
4291
4292 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
4293
4294 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
4295 UISettings::values.roms_path = QFileInfo(filename).path();
4296 BootGame(filename);
4297}
4298
4137void GMainWindow::OnCaptureScreenshot() { 4299void GMainWindow::OnCaptureScreenshot() {
4138 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4300 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4139 return; 4301 return;
@@ -4540,6 +4702,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4540 if (behavior == ReinitializeKeyBehavior::Warning) { 4702 if (behavior == ReinitializeKeyBehavior::Warning) {
4541 game_list->PopulateAsync(UISettings::values.game_dirs); 4703 game_list->PopulateAsync(UISettings::values.game_dirs);
4542 } 4704 }
4705
4706 UpdateMenuState();
4543} 4707}
4544 4708
4545bool GMainWindow::CheckSystemArchiveDecryption() { 4709bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4561,10 +4725,26 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4561 return mii_nca->GetRomFS().get() != nullptr; 4725 return mii_nca->GetRomFS().get() != nullptr;
4562} 4726}
4563 4727
4728bool GMainWindow::CheckFirmwarePresence() {
4729 constexpr u64 MiiEditId = 0x0100000000001009ull;
4730
4731 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4732 if (!bis_system) {
4733 return false;
4734 }
4735
4736 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4737 if (!mii_applet_nca) {
4738 return false;
4739 }
4740
4741 return true;
4742}
4743
4564bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4744bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4565 u64* selected_title_id, u8* selected_content_record_type) { 4745 u64* selected_title_id, u8* selected_content_record_type) {
4566 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; 4746 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
4567 boost::container::flat_map<u64, ContentInfo> available_title_ids; 4747 boost::container::flat_set<ContentInfo> available_title_ids;
4568 4748
4569 const auto RetrieveEntries = [&](FileSys::TitleType title_type, 4749 const auto RetrieveEntries = [&](FileSys::TitleType title_type,
4570 FileSys::ContentRecordType record_type) { 4750 FileSys::ContentRecordType record_type) {
@@ -4572,12 +4752,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4572 for (const auto& entry : entries) { 4752 for (const auto& entry : entries) {
4573 if (FileSys::GetBaseTitleID(entry.title_id) == program_id && 4753 if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
4574 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { 4754 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
4575 available_title_ids[entry.title_id] = {title_type, record_type}; 4755 available_title_ids.insert({entry.title_id, title_type, record_type});
4576 } 4756 }
4577 } 4757 }
4578 }; 4758 };
4579 4759
4580 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); 4760 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
4761 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
4762 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
4581 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 4763 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
4582 4764
4583 if (available_title_ids.empty()) { 4765 if (available_title_ids.empty()) {
@@ -4588,10 +4770,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4588 4770
4589 if (available_title_ids.size() > 1) { 4771 if (available_title_ids.size() > 1) {
4590 QStringList list; 4772 QStringList list;
4591 for (auto& [title_id, content_info] : available_title_ids) { 4773 for (auto& [title_id, title_type, record_type] : available_title_ids) {
4592 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); 4774 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
4593 if (content_info.first == FileSys::TitleType::Application) { 4775 if (record_type == FileSys::ContentRecordType::Program) {
4594 list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id)); 4776 list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
4777 } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
4778 list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
4779 } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
4780 list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
4595 } else { 4781 } else {
4596 list.push_back( 4782 list.push_back(
4597 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); 4783 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
@@ -4609,9 +4795,9 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4609 title_index = list.indexOf(res); 4795 title_index = list.indexOf(res);
4610 } 4796 }
4611 4797
4612 const auto selected_info = available_title_ids.nth(title_index); 4798 const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
4613 *selected_title_id = selected_info->first; 4799 *selected_title_id = title_id;
4614 *selected_content_record_type = static_cast<u8>(selected_info->second.second); 4800 *selected_content_record_type = static_cast<u8>(record_type);
4615 return true; 4801 return true;
4616} 4802}
4617 4803
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index cf191f698..2346eb3bd 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -81,6 +81,10 @@ namespace DiscordRPC {
81class DiscordInterface; 81class DiscordInterface;
82} 82}
83 83
84namespace PlayTime {
85class PlayTimeManager;
86}
87
84namespace FileSys { 88namespace FileSys {
85class ContentProvider; 89class ContentProvider;
86class ManualContentProvider; 90class ManualContentProvider;
@@ -102,6 +106,10 @@ namespace Service::NFC {
102class NfcDevice; 106class NfcDevice;
103} // namespace Service::NFC 107} // namespace Service::NFC
104 108
109namespace Service::NFP {
110enum class CabinetMode : u8;
111} // namespace Service::NFP
112
105namespace Ui { 113namespace Ui {
106class MainWindow; 114class MainWindow;
107} 115}
@@ -319,6 +327,7 @@ private slots:
319 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 327 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
320 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 328 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
321 const std::string& game_path); 329 const std::string& game_path);
330 void OnGameListRemovePlayTimeData(u64 program_id);
322 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 331 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
323 void OnGameListVerifyIntegrity(const std::string& game_path); 332 void OnGameListVerifyIntegrity(const std::string& game_path);
324 void OnGameListCopyTID(u64 program_id); 333 void OnGameListCopyTID(u64 program_id);
@@ -365,6 +374,9 @@ private slots:
365 void ResetWindowSize720(); 374 void ResetWindowSize720();
366 void ResetWindowSize900(); 375 void ResetWindowSize900();
367 void ResetWindowSize1080(); 376 void ResetWindowSize1080();
377 void OnAlbum();
378 void OnCabinet(Service::NFP::CabinetMode mode);
379 void OnMiiEdit();
368 void OnCaptureScreenshot(); 380 void OnCaptureScreenshot();
369 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 381 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
370 void OnLanguageChanged(const QString& locale); 382 void OnLanguageChanged(const QString& locale);
@@ -383,10 +395,11 @@ private:
383 void RemoveVulkanDriverPipelineCache(u64 program_id); 395 void RemoveVulkanDriverPipelineCache(u64 program_id);
384 void RemoveAllTransferableShaderCaches(u64 program_id); 396 void RemoveAllTransferableShaderCaches(u64 program_id);
385 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 397 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
398 void RemovePlayTimeData(u64 program_id);
386 void RemoveCacheStorage(u64 program_id); 399 void RemoveCacheStorage(u64 program_id);
387 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 400 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
388 u64* selected_title_id, u8* selected_content_record_type); 401 u64* selected_title_id, u8* selected_content_record_type);
389 InstallResult InstallNSPXCI(const QString& filename); 402 InstallResult InstallNSP(const QString& filename);
390 InstallResult InstallNCA(const QString& filename); 403 InstallResult InstallNCA(const QString& filename);
391 void MigrateConfigFiles(); 404 void MigrateConfigFiles();
392 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 405 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -409,6 +422,7 @@ private:
409 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 422 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
410 bool CheckDarkMode(); 423 bool CheckDarkMode();
411 bool CheckSystemArchiveDecryption(); 424 bool CheckSystemArchiveDecryption();
425 bool CheckFirmwarePresence();
412 void ConfigureFilesystemProvider(const std::string& filepath); 426 void ConfigureFilesystemProvider(const std::string& filepath);
413 427
414 QString GetTasStateDescription() const; 428 QString GetTasStateDescription() const;
@@ -421,6 +435,7 @@ private:
421 435
422 std::unique_ptr<Core::System> system; 436 std::unique_ptr<Core::System> system;
423 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; 437 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
438 std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
424 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; 439 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
425 440
426 MultiplayerState* multiplayer_state = nullptr; 441 MultiplayerState* multiplayer_state = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index e54d7d75d..88684ffb5 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -137,6 +137,15 @@
137 <property name="title"> 137 <property name="title">
138 <string>&amp;Tools</string> 138 <string>&amp;Tools</string>
139 </property> 139 </property>
140 <widget class="QMenu" name="menu_cabinet_applet">
141 <property name="title">
142 <string>&amp;Amiibo</string>
143 </property>
144 <addaction name="action_Load_Cabinet_Nickname_Owner"/>
145 <addaction name="action_Load_Cabinet_Eraser"/>
146 <addaction name="action_Load_Cabinet_Restorer"/>
147 <addaction name="action_Load_Cabinet_Formatter"/>
148 </widget>
140 <widget class="QMenu" name="menuTAS"> 149 <widget class="QMenu" name="menuTAS">
141 <property name="title"> 150 <property name="title">
142 <string>&amp;TAS</string> 151 <string>&amp;TAS</string>
@@ -150,6 +159,10 @@
150 <addaction name="action_Rederive"/> 159 <addaction name="action_Rederive"/>
151 <addaction name="action_Verify_installed_contents"/> 160 <addaction name="action_Verify_installed_contents"/>
152 <addaction name="separator"/> 161 <addaction name="separator"/>
162 <addaction name="menu_cabinet_applet"/>
163 <addaction name="action_Load_Album"/>
164 <addaction name="action_Load_Mii_Edit"/>
165 <addaction name="separator"/>
153 <addaction name="action_Capture_Screenshot"/> 166 <addaction name="action_Capture_Screenshot"/>
154 <addaction name="menuTAS"/> 167 <addaction name="menuTAS"/>
155 </widget> 168 </widget>
@@ -217,7 +230,7 @@
217 </action> 230 </action>
218 <action name="action_Verify_installed_contents"> 231 <action name="action_Verify_installed_contents">
219 <property name="text"> 232 <property name="text">
220 <string>Verify installed contents</string> 233 <string>&amp;Verify Installed Contents</string>
221 </property> 234 </property>
222 </action> 235 </action>
223 <action name="action_About"> 236 <action name="action_About">
@@ -368,6 +381,36 @@
368 <string>&amp;Capture Screenshot</string> 381 <string>&amp;Capture Screenshot</string>
369 </property> 382 </property>
370 </action> 383 </action>
384 <action name="action_Load_Album">
385 <property name="text">
386 <string>Open &amp;Album</string>
387 </property>
388 </action>
389 <action name="action_Load_Cabinet_Nickname_Owner">
390 <property name="text">
391 <string>&amp;Set Nickname and Owner</string>
392 </property>
393 </action>
394 <action name="action_Load_Cabinet_Eraser">
395 <property name="text">
396 <string>&amp;Delete Game Data</string>
397 </property>
398 </action>
399 <action name="action_Load_Cabinet_Restorer">
400 <property name="text">
401 <string>&amp;Restore Amiibo</string>
402 </property>
403 </action>
404 <action name="action_Load_Cabinet_Formatter">
405 <property name="text">
406 <string>&amp;Format Amiibo</string>
407 </property>
408 </action>
409 <action name="action_Load_Mii_Edit">
410 <property name="text">
411 <string>Open &amp;Mii Editor</string>
412 </property>
413 </action>
371 <action name="action_Configure_Tas"> 414 <action name="action_Configure_Tas">
372 <property name="text"> 415 <property name="text">
373 <string>&amp;Configure TAS...</string> 416 <string>&amp;Configure TAS...</string>
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.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/settings.h"
10#include "common/thread.h"
11#include "core/hle/service/acc/profile_manager.h"
12#include "yuzu/play_time_manager.h"
13
14namespace PlayTime {
15
16namespace {
17
18struct PlayTimeElement {
19 ProgramId program_id;
20 PlayTime play_time;
21};
22
23std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
24 const Service::Account::ProfileManager manager;
25 const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
26 if (!uuid.has_value()) {
27 return std::nullopt;
28 }
29 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
30 uuid->RawString().append(".bin");
31}
32
33[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
34 const auto filename = GetCurrentUserPlayTimePath();
35
36 if (!filename.has_value()) {
37 LOG_ERROR(Frontend, "Failed to get current user path");
38 return false;
39 }
40
41 out_play_time_db.clear();
42
43 if (Common::FS::Exists(filename.value())) {
44 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
45 Common::FS::FileType::BinaryFile};
46 if (!file.IsOpen()) {
47 LOG_ERROR(Frontend, "Failed to open play time file: {}",
48 Common::FS::PathToUTF8String(filename.value()));
49 return false;
50 }
51
52 const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
53 std::vector<PlayTimeElement> elements(num_elements);
54
55 if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
56 return false;
57 }
58
59 for (const auto& [program_id, play_time] : elements) {
60 if (program_id != 0) {
61 out_play_time_db[program_id] = play_time;
62 }
63 }
64 }
65
66 return true;
67}
68
69[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
70 const auto filename = GetCurrentUserPlayTimePath();
71
72 if (!filename.has_value()) {
73 LOG_ERROR(Frontend, "Failed to get current user path");
74 return false;
75 }
76
77 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
78 Common::FS::FileType::BinaryFile};
79 if (!file.IsOpen()) {
80 LOG_ERROR(Frontend, "Failed to open play time file: {}",
81 Common::FS::PathToUTF8String(filename.value()));
82 return false;
83 }
84
85 std::vector<PlayTimeElement> elements;
86 elements.reserve(play_time_db.size());
87
88 for (auto& [program_id, play_time] : play_time_db) {
89 if (program_id != 0) {
90 elements.push_back(PlayTimeElement{program_id, play_time});
91 }
92 }
93
94 return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
95}
96
97} // namespace
98
99PlayTimeManager::PlayTimeManager() {
100 if (!ReadPlayTimeFile(database)) {
101 LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
102 }
103}
104
105PlayTimeManager::~PlayTimeManager() {
106 Save();
107}
108
109void PlayTimeManager::SetProgramId(u64 program_id) {
110 running_program_id = program_id;
111}
112
113void PlayTimeManager::Start() {
114 play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
115}
116
117void PlayTimeManager::Stop() {
118 play_time_thread = {};
119}
120
121void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("PlayTimeReport");
123
124 using namespace std::literals::chrono_literals;
125 using std::chrono::seconds;
126 using std::chrono::steady_clock;
127
128 auto timestamp = steady_clock::now();
129
130 const auto GetDuration = [&]() -> u64 {
131 const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
132 const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
133 return static_cast<u64>(duration.count());
134 };
135
136 while (!stop_token.stop_requested()) {
137 Common::StoppableTimedWait(stop_token, 30s);
138
139 database[running_program_id] += GetDuration();
140 Save();
141 }
142}
143
144void PlayTimeManager::Save() {
145 if (!WritePlayTimeFile(database)) {
146 LOG_ERROR(Frontend, "Failed to update play time database!");
147 }
148}
149
150u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
151 auto it = database.find(program_id);
152 if (it != database.end()) {
153 return it->second;
154 } else {
155 return 0;
156 }
157}
158
159void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
160 database.erase(program_id);
161 Save();
162}
163
164QString ReadablePlayTime(qulonglong time_seconds) {
165 if (time_seconds == 0) {
166 return {};
167 }
168 const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
169 const auto time_hours = static_cast<double>(time_seconds) / 3600;
170 const bool is_minutes = time_minutes < 60;
171 const char* unit = is_minutes ? "m" : "h";
172 const auto value = is_minutes ? time_minutes : time_hours;
173
174 return QStringLiteral("%L1 %2")
175 .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
176 .arg(QString::fromUtf8(unit));
177}
178
179} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QString>
7
8#include <map>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/polyfill_thread.h"
13
14namespace PlayTime {
15
16using ProgramId = u64;
17using PlayTime = u64;
18using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
19
20class PlayTimeManager {
21public:
22 explicit PlayTimeManager();
23 ~PlayTimeManager();
24
25 YUZU_NON_COPYABLE(PlayTimeManager);
26 YUZU_NON_MOVEABLE(PlayTimeManager);
27
28 u64 GetPlayTime(u64 program_id) const;
29 void ResetProgramPlayTime(u64 program_id);
30 void SetProgramId(u64 program_id);
31 void Start();
32 void Stop();
33
34private:
35 PlayTimeDatabase database;
36 u64 running_program_id;
37 std::jthread play_time_thread;
38 void AutoTimestamp(std::stop_token stop_token);
39 void Save();
40};
41
42QString ReadablePlayTime(qulonglong time_seconds);
43
44} // namespace PlayTime
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 8efd63f31..975008159 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -103,7 +103,7 @@ struct Values {
103 true, 103 true,
104 true}; 104 true};
105 Setting<bool> mute_when_in_background{ 105 Setting<bool> mute_when_in_background{
106 linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default, 106 linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
107 true, true}; 107 true, true};
108 Setting<bool> hide_mouse{ 108 Setting<bool> hide_mouse{
109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, 109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +183,9 @@ struct Values {
183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; 183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; 184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
185 185
186 // Play time
187 Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
188
186 bool configuration_applied; 189 bool configuration_applied;
187 bool reset_to_defaults; 190 bool reset_to_defaults;
188 bool shortcut_already_warned{false}; 191 bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..61cf00176 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
5#include <cmath> 5#include <cmath>
6#include <QPainter> 6#include <QPainter>
7#include "yuzu/util/util.h" 7#include "yuzu/util/util.h"
8#ifdef _WIN32
9#include <windows.h>
10#include "common/fs/file.h"
11#endif
8 12
9QFont GetMonospaceFont() { 13QFont GetMonospaceFont() {
10 QFont font(QStringLiteral("monospace")); 14 QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
37 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); 41 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
38 return circle_pixmap; 42 return circle_pixmap;
39} 43}
44
45bool SaveIconToFile(const std::string_view path, const QImage& image) {
46#if defined(WIN32)
47#pragma pack(push, 2)
48 struct IconDir {
49 WORD id_reserved;
50 WORD id_type;
51 WORD id_count;
52 };
53
54 struct IconDirEntry {
55 BYTE width;
56 BYTE height;
57 BYTE color_count;
58 BYTE reserved;
59 WORD planes;
60 WORD bit_count;
61 DWORD bytes_in_res;
62 DWORD image_offset;
63 };
64#pragma pack(pop)
65
66 QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr int bytes_per_pixel = 4;
68 const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
69
70 BITMAPINFOHEADER info_header{};
71 info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
72 info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
73 info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
74
75 const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
76 const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
77 .height = static_cast<BYTE>(source_image.height() * 2),
78 .color_count = 0,
79 .reserved = 0,
80 .planes = 1,
81 .bit_count = bytes_per_pixel * 8,
82 .bytes_in_res =
83 static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
84 .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
85
86 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
87 Common::FS::FileType::BinaryFile);
88 if (!icon_file.IsOpen()) {
89 return false;
90 }
91
92 if (!icon_file.Write(icon_dir)) {
93 return false;
94 }
95 if (!icon_file.Write(icon_entry)) {
96 return false;
97 }
98 if (!icon_file.Write(info_header)) {
99 return false;
100 }
101
102 for (int y = 0; y < image.height(); y++) {
103 const auto* line = source_image.scanLine(source_image.height() - 1 - y);
104 std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
105 std::memcpy(line_data.data(), line, line_data.size());
106 if (!icon_file.Write(line_data)) {
107 return false;
108 }
109 }
110 icon_file.Close();
111
112 return true;
113#else
114 return false;
115#endif
116}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
7#include <QString> 7#include <QString>
8 8
9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. 9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
10QFont GetMonospaceFont(); 10[[nodiscard]] QFont GetMonospaceFont();
11 11
12/// Convert a size in bytes into a readable format (KiB, MiB, etc.) 12/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
13QString ReadableByteSize(qulonglong size); 13[[nodiscard]] QString ReadableByteSize(qulonglong size);
14 14
15/** 15/**
16 * Creates a circle pixmap from a specified color 16 * Creates a circle pixmap from a specified color
17 * @param color The color the pixmap shall have 17 * @param color The color the pixmap shall have
18 * @return QPixmap circle pixmap 18 * @return QPixmap circle pixmap
19 */ 19 */
20QPixmap CreateCirclePixmapFromColor(const QColor& color); 20[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
21
22/**
23 * Saves a windows icon to a file
24 * @param path The icons path
25 * @param image The image to save
26 * @return bool If the operation succeeded
27 */
28[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);