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.kts18
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-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/DriverAdapter.kt117
-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/DriverManagerFragment.kt186
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt75
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt95
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt89
-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.kt79
-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/DriverViewModel.kt158
-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.kt328
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt164
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt228
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt112
-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_build.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_delete.xml9
-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_driver_option.xml89
-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_driver_manager.xml48
-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.xml14
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml3
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml3
-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.xml3
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml3
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/integers.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml31
-rw-r--r--src/android/build.gradle.kts4
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp9
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h2
-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/audio_core/sink/sink_stream.cpp4
-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/common_funcs.h4
-rw-r--r--src/common/elf.h8
-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/polyfill_thread.h21
-rw-r--r--src/common/settings.cpp15
-rw-r--r--src/common/settings.h9
-rw-r--r--src/common/settings_enums.h2
-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.cpp34
-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/kernel.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/service/acc/acc.cpp43
-rw-r--r--src/core/hle/service/acc/acc.h3
-rw-r--r--src/core/hle/service/acc/acc_su.cpp6
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/am/am.cpp523
-rw-r--r--src/core/hle/service/am/am.h73
-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.cpp386
-rw-r--r--src/core/hle/service/caps/caps_manager.h79
-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.cpp146
-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/jit/jit_context.cpp36
-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/udp_client.cpp1
-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.h64
-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.cpp24
-rw-r--r--src/video_core/engines/draw_manager.h3
-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/host1x/codecs/codec.cpp10
-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/gl_texture_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-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.cpp175
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h9
-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/texture_cache.h1
-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.cpp29
-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.txt4
-rw-r--r--src/yuzu/applets/qt_controller.cpp39
-rw-r--r--src/yuzu/applets/qt_controller.h6
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_input.cpp36
-rw-r--r--src/yuzu/configuration/configure_input.h1
-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.cpp11
-rw-r--r--src/yuzu/game_list.cpp23
-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.cpp30
-rw-r--r--src/yuzu/game_list_worker.h11
-rw-r--r--src/yuzu/main.cpp417
-rw-r--r--src/yuzu/main.h35
-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.h16
-rw-r--r--src/yuzu/util/util.cpp102
-rw-r--r--src/yuzu/util/util.h14
287 files changed, 12333 insertions, 2228 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..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
27 namespace = "org.yuzu.yuzu_emu" 27 namespace = "org.yuzu.yuzu_emu"
28 28
29 compileSdkVersion = "android-34" 29 compileSdkVersion = "android-34"
30 ndkVersion = "25.2.9519653" 30 ndkVersion = "26.1.10909125"
31 31
32 buildFeatures { 32 buildFeatures {
33 viewBinding = true 33 viewBinding = true
@@ -203,23 +203,23 @@ ktlint {
203} 203}
204 204
205dependencies { 205dependencies {
206 implementation("androidx.core:core-ktx:1.10.1") 206 implementation("androidx.core:core-ktx:1.12.0")
207 implementation("androidx.appcompat:appcompat:1.6.1") 207 implementation("androidx.appcompat:appcompat:1.6.1")
208 implementation("androidx.recyclerview:recyclerview:1.3.0") 208 implementation("androidx.recyclerview:recyclerview:1.3.1")
209 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 209 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
210 implementation("androidx.fragment:fragment-ktx:1.6.0") 210 implementation("androidx.fragment:fragment-ktx:1.6.1")
211 implementation("androidx.documentfile:documentfile:1.0.1") 211 implementation("androidx.documentfile:documentfile:1.0.1")
212 implementation("com.google.android.material:material:1.9.0") 212 implementation("com.google.android.material:material:1.9.0")
213 implementation("androidx.preference:preference:1.2.0") 213 implementation("androidx.preference:preference-ktx:1.2.1")
214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
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")
221 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") 221 implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
222 implementation("androidx.navigation:navigation-ui-ktx:2.6.0") 222 implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
223 implementation("info.debatty:java-string-similarity:2.0.0") 223 implementation("info.debatty:java-string-similarity:2.0.0")
224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
225} 225}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 832c08e15..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
28 android:appCategory="game" 28 android:appCategory="game"
29 android:localeConfig="@xml/locales_config" 29 android:localeConfig="@xml/locales_config"
30 android:banner="@drawable/tv_banner" 30 android:banner="@drawable/tv_banner"
31 android:extractNativeLibs="true"
32 android:fullBackupContent="@xml/data_extraction_rules" 31 android:fullBackupContent="@xml/data_extraction_rules"
33 android:dataExtractionRules="@xml/data_extraction_rules_api_31" 32 android:dataExtractionRules="@xml/data_extraction_rules_api_31"
34 android:enableOnBackInvokedCallback="true"> 33 android:enableOnBackInvokedCallback="true">
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..115f72710 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
@@ -15,13 +15,9 @@ import androidx.annotation.Keep
15import androidx.fragment.app.DialogFragment 15import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import java.lang.ref.WeakReference 17import java.lang.ref.WeakReference
18import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
19import org.yuzu.yuzu_emu.activities.EmulationActivity 18import org.yuzu.yuzu_emu.activities.EmulationActivity
20import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath 19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
21import org.yuzu.yuzu_emu.utils.FileUtil.exists 20import org.yuzu.yuzu_emu.utils.FileUtil
22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
25import org.yuzu.yuzu_emu.utils.Log 21import org.yuzu.yuzu_emu.utils.Log
26import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 22import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
27 23
@@ -75,7 +71,7 @@ object NativeLibrary {
75 return if (isNativePath(path!!)) { 71 return if (isNativePath(path!!)) {
76 YuzuApplication.documentsTree!!.openContentUri(path, openmode) 72 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
77 } else { 73 } else {
78 openContentUri(appContext, path, openmode) 74 FileUtil.openContentUri(path, openmode)
79 } 75 }
80 } 76 }
81 77
@@ -85,7 +81,7 @@ object NativeLibrary {
85 return if (isNativePath(path!!)) { 81 return if (isNativePath(path!!)) {
86 YuzuApplication.documentsTree!!.getFileSize(path) 82 YuzuApplication.documentsTree!!.getFileSize(path)
87 } else { 83 } else {
88 getFileSize(appContext, path) 84 FileUtil.getFileSize(path)
89 } 85 }
90 } 86 }
91 87
@@ -95,7 +91,7 @@ object NativeLibrary {
95 return if (isNativePath(path!!)) { 91 return if (isNativePath(path!!)) {
96 YuzuApplication.documentsTree!!.exists(path) 92 YuzuApplication.documentsTree!!.exists(path)
97 } else { 93 } else {
98 exists(appContext, path) 94 FileUtil.exists(path)
99 } 95 }
100 } 96 }
101 97
@@ -105,7 +101,7 @@ object NativeLibrary {
105 return if (isNativePath(path!!)) { 101 return if (isNativePath(path!!)) {
106 YuzuApplication.documentsTree!!.isDirectory(path) 102 YuzuApplication.documentsTree!!.isDirectory(path)
107 } else { 103 } else {
108 isDirectory(appContext, path) 104 FileUtil.isDirectory(path)
109 } 105 }
110 } 106 }
111 107
@@ -247,7 +243,12 @@ object NativeLibrary {
247 243
248 external fun setAppDirectory(directory: String) 244 external fun setAppDirectory(directory: String)
249 245
250 external fun installFileToNand(filename: String): Int 246 /**
247 * Installs a nsp or xci file to nand
248 * @param filename String representation of file uri
249 * @param extension Lowercase string representation of file extension without "."
250 */
251 external fun installFileToNand(filename: String, extension: String): Int
251 252
252 external fun initializeGpuDriver( 253 external fun initializeGpuDriver(
253 hookLibDir: String?, 254 hookLibDir: String?,
@@ -512,6 +513,11 @@ object NativeLibrary {
512 external fun submitInlineKeyboardInput(key_code: Int) 513 external fun submitInlineKeyboardInput(key_code: Int)
513 514
514 /** 515 /**
516 * Creates a generic user directory if it doesn't exist already
517 */
518 external fun initializeEmptyUserDirectory()
519
520 /**
515 * Button type for use in onTouchEvent 521 * Button type for use in onTouchEvent
516 */ 522 */
517 object ButtonType { 523 object ButtonType {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 9561748cb..8c053670c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -47,7 +47,7 @@ class YuzuApplication : Application() {
47 application = this 47 application = this
48 documentsTree = DocumentsTree() 48 documentsTree = DocumentsTree()
49 DirectoryInitialization.start() 49 DirectoryInitialization.start()
50 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters()
51 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
52 52
53 createNotificationChannels() 53 createNotificationChannels()
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/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
new file mode 100644
index 000000000..0e818cab9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -0,0 +1,117 @@
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.text.TextUtils
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.recyclerview.widget.AsyncDifferConfig
11import androidx.recyclerview.widget.DiffUtil
12import androidx.recyclerview.widget.ListAdapter
13import androidx.recyclerview.widget.RecyclerView
14import org.yuzu.yuzu_emu.R
15import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
16import org.yuzu.yuzu_emu.model.DriverViewModel
17import org.yuzu.yuzu_emu.utils.GpuDriverHelper
18import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
19
20class DriverAdapter(private val driverViewModel: DriverViewModel) :
21 ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
22 AsyncDifferConfig.Builder(DiffCallback()).build()
23 ) {
24 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
25 val binding =
26 CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
27 return DriverViewHolder(binding)
28 }
29
30 override fun getItemCount(): Int = currentList.size
31
32 override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
33 holder.bind(currentList[position])
34
35 private fun onSelectDriver(position: Int) {
36 driverViewModel.setSelectedDriverIndex(position)
37 notifyItemChanged(driverViewModel.previouslySelectedDriver)
38 notifyItemChanged(driverViewModel.selectedDriver)
39 }
40
41 private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
42 if (driverViewModel.selectedDriver > position) {
43 driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
44 }
45 if (GpuDriverHelper.customDriverData == driverData.second) {
46 driverViewModel.setSelectedDriverIndex(0)
47 }
48 driverViewModel.driversToDelete.add(driverData.first)
49 driverViewModel.removeDriver(driverData)
50 notifyItemRemoved(position)
51 notifyItemChanged(driverViewModel.selectedDriver)
52 }
53
54 inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
55 RecyclerView.ViewHolder(binding.root) {
56 private lateinit var driverData: Pair<String, GpuDriverMetadata>
57
58 fun bind(driverData: Pair<String, GpuDriverMetadata>) {
59 this.driverData = driverData
60 val driver = driverData.second
61
62 binding.apply {
63 radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
64 root.setOnClickListener {
65 onSelectDriver(bindingAdapterPosition)
66 }
67 buttonDelete.setOnClickListener {
68 onDeleteDriver(driverData, bindingAdapterPosition)
69 }
70
71 // Delay marquee by 3s
72 title.postDelayed(
73 {
74 title.isSelected = true
75 title.ellipsize = TextUtils.TruncateAt.MARQUEE
76 version.isSelected = true
77 version.ellipsize = TextUtils.TruncateAt.MARQUEE
78 description.isSelected = true
79 description.ellipsize = TextUtils.TruncateAt.MARQUEE
80 },
81 3000
82 )
83 if (driver.name == null) {
84 title.setText(R.string.system_gpu_driver)
85 description.text = ""
86 version.text = ""
87 version.visibility = View.GONE
88 description.visibility = View.GONE
89 buttonDelete.visibility = View.GONE
90 } else {
91 title.text = driver.name
92 version.text = driver.version
93 description.text = driver.description
94 version.visibility = View.VISIBLE
95 description.visibility = View.VISIBLE
96 buttonDelete.visibility = View.VISIBLE
97 }
98 }
99 }
100 }
101
102 private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
103 override fun areItemsTheSame(
104 oldItem: Pair<String, GpuDriverMetadata>,
105 newItem: Pair<String, GpuDriverMetadata>
106 ): Boolean {
107 return oldItem.first == newItem.first
108 }
109
110 override fun areContentsTheSame(
111 oldItem: Pair<String, GpuDriverMetadata>,
112 newItem: Pair<String, GpuDriverMetadata>
113 ): Boolean {
114 return oldItem.second == newItem.second
115 }
116 }
117}
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/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
new file mode 100644
index 000000000..df21d74b2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,186 @@
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.activity.result.contract.ActivityResultContracts
11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.lifecycleScope
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.GridLayoutManager
19import com.google.android.material.transition.MaterialSharedAxis
20import kotlinx.coroutines.flow.collectLatest
21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.adapters.DriverAdapter
24import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
25import org.yuzu.yuzu_emu.model.DriverViewModel
26import org.yuzu.yuzu_emu.model.HomeViewModel
27import org.yuzu.yuzu_emu.utils.FileUtil
28import org.yuzu.yuzu_emu.utils.GpuDriverHelper
29import java.io.File
30import java.io.IOException
31
32class DriverManagerFragment : Fragment() {
33 private var _binding: FragmentDriverManagerBinding? = null
34 private val binding get() = _binding!!
35
36 private val homeViewModel: HomeViewModel by activityViewModels()
37 private val driverViewModel: DriverViewModel by activityViewModels()
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
42 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
43 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
44 }
45
46 override fun onCreateView(
47 inflater: LayoutInflater,
48 container: ViewGroup?,
49 savedInstanceState: Bundle?
50 ): View {
51 _binding = FragmentDriverManagerBinding.inflate(inflater)
52 return binding.root
53 }
54
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 super.onViewCreated(view, savedInstanceState)
57 homeViewModel.setNavigationVisibility(visible = false, animated = true)
58 homeViewModel.setStatusBarShadeVisibility(visible = false)
59
60 if (!driverViewModel.isInteractionAllowed) {
61 DriversLoadingDialogFragment().show(
62 childFragmentManager,
63 DriversLoadingDialogFragment.TAG
64 )
65 }
66
67 binding.toolbarDrivers.setNavigationOnClickListener {
68 binding.root.findNavController().popBackStack()
69 }
70
71 binding.buttonInstall.setOnClickListener {
72 getDriver.launch(arrayOf("application/zip"))
73 }
74
75 binding.listDrivers.apply {
76 layoutManager = GridLayoutManager(
77 requireContext(),
78 resources.getInteger(R.integer.grid_columns)
79 )
80 adapter = DriverAdapter(driverViewModel)
81 }
82
83 viewLifecycleOwner.lifecycleScope.apply {
84 launch {
85 driverViewModel.driverList.collectLatest {
86 (binding.listDrivers.adapter as DriverAdapter).submitList(it)
87 }
88 }
89 launch {
90 driverViewModel.newDriverInstalled.collect {
91 if (_binding != null && it) {
92 (binding.listDrivers.adapter as DriverAdapter).apply {
93 notifyItemChanged(driverViewModel.previouslySelectedDriver)
94 notifyItemChanged(driverViewModel.selectedDriver)
95 driverViewModel.setNewDriverInstalled(false)
96 }
97 }
98 }
99 }
100 }
101
102 setInsets()
103 }
104
105 // Start installing requested driver
106 override fun onStop() {
107 super.onStop()
108 driverViewModel.onCloseDriverManager()
109 }
110
111 private fun setInsets() =
112 ViewCompat.setOnApplyWindowInsetsListener(
113 binding.root
114 ) { _: View, windowInsets: WindowInsetsCompat ->
115 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
116 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
117
118 val leftInsets = barInsets.left + cutoutInsets.left
119 val rightInsets = barInsets.right + cutoutInsets.right
120
121 val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
122 mlpAppBar.leftMargin = leftInsets
123 mlpAppBar.rightMargin = rightInsets
124 binding.toolbarDrivers.layoutParams = mlpAppBar
125
126 val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
127 mlplistDrivers.leftMargin = leftInsets
128 mlplistDrivers.rightMargin = rightInsets
129 binding.listDrivers.layoutParams = mlplistDrivers
130
131 val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
132 val mlpFab =
133 binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
134 mlpFab.leftMargin = leftInsets + fabSpacing
135 mlpFab.rightMargin = rightInsets + fabSpacing
136 mlpFab.bottomMargin = barInsets.bottom + fabSpacing
137 binding.buttonInstall.layoutParams = mlpFab
138
139 binding.listDrivers.updatePadding(
140 bottom = barInsets.bottom +
141 resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
142 )
143
144 windowInsets
145 }
146
147 private val getDriver =
148 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
149 if (result == null) {
150 return@registerForActivityResult
151 }
152
153 IndeterminateProgressDialogFragment.newInstance(
154 requireActivity(),
155 R.string.installing_driver,
156 false
157 ) {
158 val driverPath =
159 "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
160 val driverFile = File(driverPath)
161
162 // Ignore file exceptions when a user selects an invalid zip
163 try {
164 if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
165 throw IOException("Driver failed validation!")
166 }
167 } catch (_: IOException) {
168 if (driverFile.exists()) {
169 driverFile.delete()
170 }
171 return@newInstance getString(R.string.select_gpu_driver_error)
172 }
173
174 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
175 val driverInList =
176 driverViewModel.driverList.value.firstOrNull { it.second == driverData }
177 if (driverInList != null) {
178 return@newInstance getString(R.string.driver_already_installed)
179 } else {
180 driverViewModel.addDriver(Pair(driverPath, driverData))
181 driverViewModel.setNewDriverInstalled(true)
182 }
183 return@newInstance Any()
184 }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
185 }
186}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
new file mode 100644
index 000000000..f8c34346a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -0,0 +1,75 @@
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.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.fragment.app.DialogFragment
12import androidx.fragment.app.activityViewModels
13import androidx.lifecycle.Lifecycle
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
18import org.yuzu.yuzu_emu.R
19import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
20import org.yuzu.yuzu_emu.model.DriverViewModel
21
22class DriversLoadingDialogFragment : DialogFragment() {
23 private val driverViewModel: DriverViewModel by activityViewModels()
24
25 private lateinit var binding: DialogProgressBarBinding
26
27 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 binding = DialogProgressBarBinding.inflate(layoutInflater)
29 binding.progressBar.isIndeterminate = true
30
31 isCancelable = false
32
33 return MaterialAlertDialogBuilder(requireContext())
34 .setTitle(R.string.loading)
35 .setView(binding.root)
36 .create()
37 }
38
39 override fun onCreateView(
40 inflater: LayoutInflater,
41 container: ViewGroup?,
42 savedInstanceState: Bundle?
43 ): View = binding.root
44
45 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
46 super.onViewCreated(view, savedInstanceState)
47 viewLifecycleOwner.lifecycleScope.apply {
48 launch {
49 repeatOnLifecycle(Lifecycle.State.RESUMED) {
50 driverViewModel.areDriversLoading.collect { checkForDismiss() }
51 }
52 }
53 launch {
54 repeatOnLifecycle(Lifecycle.State.RESUMED) {
55 driverViewModel.isDriverReady.collect { checkForDismiss() }
56 }
57 }
58 launch {
59 repeatOnLifecycle(Lifecycle.State.RESUMED) {
60 driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
61 }
62 }
63 }
64 }
65
66 private fun checkForDismiss() {
67 if (driverViewModel.isInteractionAllowed) {
68 dismiss()
69 }
70 }
71
72 companion object {
73 const val TAG = "DriversLoadingDialogFragment"
74 }
75}
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..598a9d42b 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
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collect
42import kotlinx.coroutines.flow.collectLatest 43import kotlinx.coroutines.flow.collectLatest
43import kotlinx.coroutines.launch 44import kotlinx.coroutines.launch
44import org.yuzu.yuzu_emu.HomeNavigationDirections 45import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -50,10 +51,12 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 51import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
51import org.yuzu.yuzu_emu.features.settings.model.IntSetting 52import org.yuzu.yuzu_emu.features.settings.model.IntSetting
52import org.yuzu.yuzu_emu.features.settings.model.Settings 53import org.yuzu.yuzu_emu.features.settings.model.Settings
54import org.yuzu.yuzu_emu.model.DriverViewModel
53import org.yuzu.yuzu_emu.model.Game 55import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 56import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 57import org.yuzu.yuzu_emu.overlay.InputOverlay
56import org.yuzu.yuzu_emu.utils.* 58import org.yuzu.yuzu_emu.utils.*
59import java.lang.NullPointerException
57 60
58class EmulationFragment : Fragment(), SurfaceHolder.Callback { 61class EmulationFragment : Fragment(), SurfaceHolder.Callback {
59 private lateinit var preferences: SharedPreferences 62 private lateinit var preferences: SharedPreferences
@@ -69,6 +72,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
69 private lateinit var game: Game 72 private lateinit var game: Game
70 73
71 private val emulationViewModel: EmulationViewModel by activityViewModels() 74 private val emulationViewModel: EmulationViewModel by activityViewModels()
75 private val driverViewModel: DriverViewModel by activityViewModels()
72 76
73 private var isInFoldableLayout = false 77 private var isInFoldableLayout = false
74 78
@@ -105,10 +109,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
105 null 109 null
106 } 110 }
107 } 111 }
108 game = if (args.game != null) { 112
109 args.game!! 113 try {
110 } else { 114 game = if (args.game != null) {
111 intentGame ?: error("[EmulationFragment] No bootable game present!") 115 args.game!!
116 } else {
117 intentGame!!
118 }
119 } catch (e: NullPointerException) {
120 Toast.makeText(
121 requireContext(),
122 R.string.no_game_present,
123 Toast.LENGTH_SHORT
124 ).show()
125 requireActivity().finish()
126 return
112 } 127 }
113 128
114 // So this fragment doesn't restart on configuration changes; i.e. rotation. 129 // So this fragment doesn't restart on configuration changes; i.e. rotation.
@@ -132,6 +147,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
132 // This is using the correct scope, lint is just acting up 147 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector") 148 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 149 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
150 super.onViewCreated(view, savedInstanceState)
151 if (requireActivity().isFinishing) {
152 return
153 }
154
135 binding.surfaceEmulation.holder.addCallback(this) 155 binding.surfaceEmulation.holder.addCallback(this)
136 binding.showFpsText.setTextColor(Color.YELLOW) 156 binding.showFpsText.setTextColor(Color.YELLOW)
137 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } 157 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
@@ -282,29 +302,43 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
282 } 302 }
283 } 303 }
284 } 304 }
305 launch {
306 repeatOnLifecycle(Lifecycle.State.RESUMED) {
307 driverViewModel.isDriverReady.collect {
308 if (it && !emulationState.isRunning) {
309 if (!DirectoryInitialization.areDirectoriesReady) {
310 DirectoryInitialization.start()
311 }
312
313 updateScreenLayout()
314
315 emulationState.run(emulationActivity!!.isActivityRecreated)
316 }
317 }
318 }
319 }
285 } 320 }
286 } 321 }
287 322
288 override fun onConfigurationChanged(newConfig: Configuration) { 323 override fun onConfigurationChanged(newConfig: Configuration) {
289 super.onConfigurationChanged(newConfig) 324 super.onConfigurationChanged(newConfig)
325 if (_binding == null) {
326 return
327 }
328
329 updateScreenLayout()
290 if (emulationActivity?.isInPictureInPictureMode == true) { 330 if (emulationActivity?.isInPictureInPictureMode == true) {
291 if (binding.drawerLayout.isOpen) { 331 if (binding.drawerLayout.isOpen) {
292 binding.drawerLayout.close() 332 binding.drawerLayout.close()
293 } 333 }
294 if (EmulationMenuSettings.showOverlay) { 334 if (EmulationMenuSettings.showOverlay) {
295 binding.surfaceInputOverlay.post { 335 binding.surfaceInputOverlay.visibility = View.INVISIBLE
296 binding.surfaceInputOverlay.visibility = View.VISIBLE
297 }
298 } 336 }
299 } else { 337 } else {
300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { 338 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
301 binding.surfaceInputOverlay.post { 339 binding.surfaceInputOverlay.visibility = View.VISIBLE
302 binding.surfaceInputOverlay.visibility = View.VISIBLE
303 }
304 } else { 340 } else {
305 binding.surfaceInputOverlay.post { 341 binding.surfaceInputOverlay.visibility = View.INVISIBLE
306 binding.surfaceInputOverlay.visibility = View.INVISIBLE
307 }
308 } 342 }
309 if (!isInFoldableLayout) { 343 if (!isInFoldableLayout) {
310 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 344 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -316,19 +350,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
316 } 350 }
317 } 351 }
318 352
319 override fun onResume() {
320 super.onResume()
321 if (!DirectoryInitialization.areDirectoriesReady) {
322 DirectoryInitialization.start()
323 }
324
325 updateScreenLayout()
326
327 emulationState.run(emulationActivity!!.isActivityRecreated)
328 }
329
330 override fun onPause() { 353 override fun onPause() {
331 if (emulationState.isRunning) { 354 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
332 emulationState.pause() 355 emulationState.pause()
333 } 356 }
334 super.onPause() 357 super.onPause()
@@ -394,16 +417,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
394 } 417 }
395 418
396 private fun updateScreenLayout() { 419 private fun updateScreenLayout() {
397 binding.surfaceEmulation.setAspectRatio( 420 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() 421 emulationActivity?.buildPictureInPictureParams()
408 updateOrientation() 422 updateOrientation()
409 } 423 }
@@ -693,7 +707,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
693 private class EmulationState(private val gamePath: String) { 707 private class EmulationState(private val gamePath: String) {
694 private var state: State 708 private var state: State
695 private var surface: Surface? = null 709 private var surface: Surface? = null
696 private var runWhenSurfaceIsValid = false
697 710
698 init { 711 init {
699 // Starting state is stopped. 712 // Starting state is stopped.
@@ -751,8 +764,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
751 // If the surface is set, run now. Otherwise, wait for it to get set. 764 // If the surface is set, run now. Otherwise, wait for it to get set.
752 if (surface != null) { 765 if (surface != null) {
753 runWithValidSurface() 766 runWithValidSurface()
754 } else {
755 runWhenSurfaceIsValid = true
756 } 767 }
757 } 768 }
758 769
@@ -760,7 +771,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
760 @Synchronized 771 @Synchronized
761 fun newSurface(surface: Surface?) { 772 fun newSurface(surface: Surface?) {
762 this.surface = surface 773 this.surface = surface
763 if (runWhenSurfaceIsValid) { 774 if (this.surface != null) {
764 runWithValidSurface() 775 runWithValidSurface()
765 } 776 }
766 } 777 }
@@ -788,10 +799,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
788 } 799 }
789 800
790 private fun runWithValidSurface() { 801 private fun runWithValidSurface() {
791 runWhenSurfaceIsValid = false 802 NativeLibrary.surfaceChanged(surface)
792 when (state) { 803 when (state) {
793 State.STOPPED -> { 804 State.STOPPED -> {
794 NativeLibrary.surfaceChanged(surface)
795 val emulationThread = Thread({ 805 val emulationThread = Thread({
796 Log.debug("[EmulationFragment] Starting emulation thread.") 806 Log.debug("[EmulationFragment] Starting emulation thread.")
797 NativeLibrary.run(gamePath) 807 NativeLibrary.run(gamePath)
@@ -801,7 +811,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
801 811
802 State.PAUSED -> { 812 State.PAUSED -> {
803 Log.debug("[EmulationFragment] Resuming emulation.") 813 Log.debug("[EmulationFragment] Resuming emulation.")
804 NativeLibrary.surfaceChanged(surface)
805 NativeLibrary.unpauseEmulation() 814 NativeLibrary.unpauseEmulation()
806 } 815 }
807 816
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..fd9785075 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
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
5 5
6import android.Manifest 6import android.Manifest
7import android.content.ActivityNotFoundException 7import android.content.ActivityNotFoundException
8import android.content.DialogInterface
9import android.content.Intent 8import android.content.Intent
10import android.content.pm.PackageManager 9import android.content.pm.PackageManager
11import android.os.Bundle 10import android.os.Bundle
@@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels
28import androidx.navigation.findNavController 27import androidx.navigation.findNavController
29import androidx.navigation.fragment.findNavController 28import androidx.navigation.fragment.findNavController
30import androidx.recyclerview.widget.LinearLayoutManager 29import androidx.recyclerview.widget.LinearLayoutManager
31import com.google.android.material.dialog.MaterialAlertDialogBuilder
32import com.google.android.material.transition.MaterialSharedAxis 30import com.google.android.material.transition.MaterialSharedAxis
33import org.yuzu.yuzu_emu.BuildConfig 31import org.yuzu.yuzu_emu.BuildConfig
34import org.yuzu.yuzu_emu.HomeNavigationDirections 32import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 36import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 37import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.model.DriverViewModel
40import org.yuzu.yuzu_emu.model.HomeSetting 39import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 40import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity 41import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
50 private lateinit var mainActivity: MainActivity 49 private lateinit var mainActivity: MainActivity
51 50
52 private val homeViewModel: HomeViewModel by activityViewModels() 51 private val homeViewModel: HomeViewModel by activityViewModels()
52 private val driverViewModel: DriverViewModel by activityViewModels()
53 53
54 override fun onCreate(savedInstanceState: Bundle?) { 54 override fun onCreate(savedInstanceState: Bundle?) {
55 super.onCreate(savedInstanceState) 55 super.onCreate(savedInstanceState)
@@ -107,29 +107,28 @@ class HomeSettingsFragment : Fragment() {
107 ) 107 )
108 add( 108 add(
109 HomeSetting( 109 HomeSetting(
110 R.string.install_gpu_driver, 110 R.string.gpu_driver_manager,
111 R.string.install_gpu_driver_description, 111 R.string.install_gpu_driver_description,
112 R.drawable.ic_exit, 112 R.drawable.ic_build,
113 { driverInstaller() }, 113 {
114 binding.root.findNavController()
115 .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
116 },
114 { GpuDriverHelper.supportsCustomDriverLoading() }, 117 { GpuDriverHelper.supportsCustomDriverLoading() },
115 R.string.custom_driver_not_supported, 118 R.string.custom_driver_not_supported,
116 R.string.custom_driver_not_supported_description 119 R.string.custom_driver_not_supported_description,
117 ) 120 driverViewModel.selectedDriverMetadata
118 )
119 add(
120 HomeSetting(
121 R.string.install_amiibo_keys,
122 R.string.install_amiibo_keys_description,
123 R.drawable.ic_nfc,
124 { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
125 ) 121 )
126 ) 122 )
127 add( 123 add(
128 HomeSetting( 124 HomeSetting(
129 R.string.install_game_content, 125 R.string.manage_yuzu_data,
130 R.string.install_game_content_description, 126 R.string.manage_yuzu_data_description,
131 R.drawable.ic_system_update_alt, 127 R.drawable.ic_install,
132 { mainActivity.installGameUpdate.launch(arrayOf("*/*")) } 128 {
129 binding.root.findNavController()
130 .navigate(R.id.action_homeSettingsFragment_to_installableFragment)
131 }
133 ) 132 )
134 ) 133 )
135 add( 134 add(
@@ -150,35 +149,6 @@ class HomeSettingsFragment : Fragment() {
150 ) 149 )
151 add( 150 add(
152 HomeSetting( 151 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, 152 R.string.share_log,
183 R.string.share_log_description, 153 R.string.share_log_description,
184 R.drawable.ic_log, 154 R.drawable.ic_log,
@@ -326,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
326 } 296 }
327 } 297 }
328 298
329 private fun driverInstaller() {
330 // Get the driver name for the dialog message.
331 var driverName = GpuDriverHelper.customDriverName
332 if (driverName == null) {
333 driverName = getString(R.string.system_gpu_driver)
334 }
335
336 MaterialAlertDialogBuilder(requireContext())
337 .setTitle(getString(R.string.select_gpu_driver_title))
338 .setMessage(driverName)
339 .setNegativeButton(android.R.string.cancel, null)
340 .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
341 GpuDriverHelper.installDefaultDriver(requireContext())
342 Toast.makeText(
343 requireContext(),
344 R.string.select_gpu_driver_use_default,
345 Toast.LENGTH_SHORT
346 ).show()
347 }
348 .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
349 mainActivity.getDriver.launch(arrayOf("application/zip"))
350 }
351 .show()
352 }
353
354 private fun shareLog() { 299 private fun shareLog() {
355 val file = DocumentFile.fromSingleUri( 300 val file = DocumentFile.fromSingleUri(
356 mainActivity, 301 mainActivity,
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..7e467814d 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,8 +9,9 @@ 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.AppCompatActivity 12import androidx.appcompat.app.AlertDialog
13import androidx.fragment.app.DialogFragment 13import androidx.fragment.app.DialogFragment
14import androidx.fragment.app.FragmentActivity
14import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
15import androidx.lifecycle.Lifecycle 16import androidx.lifecycle.Lifecycle
16import androidx.lifecycle.ViewModelProvider 17import androidx.lifecycle.ViewModelProvider
@@ -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,41 +61,74 @@ 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 else -> {
83 // Do nothing
84 }
85 }
86 taskViewModel.clear()
87 }
88 }
89 }
90 }
91 launch {
92 repeatOnLifecycle(Lifecycle.State.CREATED) {
93 taskViewModel.cancelled.collect {
94 if (it) {
95 dialog?.setTitle(R.string.cancelling)
69 } 96 }
70 taskViewModel.clear()
71 } 97 }
72 } 98 }
73 } 99 }
74 } 100 }
75 } 101 }
76 102
103 // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
104 // Setting the OnClickListener again after the dialog is shown overrides this behavior.
105 override fun onResume() {
106 super.onResume()
107 val alertDialog = dialog as AlertDialog
108 val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
109 negativeButton.setOnClickListener {
110 alertDialog.setTitle(getString(R.string.cancelling))
111 taskViewModel.setCancelled(true)
112 }
113 }
114
77 companion object { 115 companion object {
78 const val TAG = "IndeterminateProgressDialogFragment" 116 const val TAG = "IndeterminateProgressDialogFragment"
79 117
80 private const val TITLE = "Title" 118 private const val TITLE = "Title"
119 private const val CANCELLABLE = "Cancellable"
81 120
82 fun newInstance( 121 fun newInstance(
83 activity: AppCompatActivity, 122 activity: FragmentActivity,
84 titleId: Int, 123 titleId: Int,
124 cancellable: Boolean = false,
85 task: () -> Any 125 task: () -> Any
86 ): IndeterminateProgressDialogFragment { 126 ): IndeterminateProgressDialogFragment {
87 val dialog = IndeterminateProgressDialogFragment() 127 val dialog = IndeterminateProgressDialogFragment()
88 val args = Bundle() 128 val args = Bundle()
89 ViewModelProvider(activity)[TaskViewModel::class.java].task = task 129 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
90 args.putInt(TITLE, titleId) 130 args.putInt(TITLE, titleId)
131 args.putBoolean(CANCELLABLE, cancellable)
91 dialog.arguments = args 132 dialog.arguments = args
92 return dialog 133 return dialog
93 } 134 }
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/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
new file mode 100644
index 000000000..62945ad65
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -0,0 +1,158 @@
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
7import androidx.lifecycle.viewModelScope
8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch
12import kotlinx.coroutines.withContext
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.YuzuApplication
15import org.yuzu.yuzu_emu.utils.FileUtil
16import org.yuzu.yuzu_emu.utils.GpuDriverHelper
17import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
18import java.io.BufferedOutputStream
19import java.io.File
20
21class DriverViewModel : ViewModel() {
22 private val _areDriversLoading = MutableStateFlow(false)
23 val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
24
25 private val _isDriverReady = MutableStateFlow(true)
26 val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
27
28 private val _isDeletingDrivers = MutableStateFlow(false)
29 val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
30
31 private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
32 val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
33
34 var previouslySelectedDriver = 0
35 var selectedDriver = -1
36
37 private val _selectedDriverMetadata =
38 MutableStateFlow(
39 GpuDriverHelper.customDriverData.name
40 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
41 )
42 val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
43
44 private val _newDriverInstalled = MutableStateFlow(false)
45 val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
46
47 val driversToDelete = mutableListOf<String>()
48
49 val isInteractionAllowed
50 get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
51
52 init {
53 _areDriversLoading.value = true
54 viewModelScope.launch {
55 withContext(Dispatchers.IO) {
56 val drivers = GpuDriverHelper.getDrivers()
57 val currentDriverMetadata = GpuDriverHelper.customDriverData
58 for (i in drivers.indices) {
59 if (drivers[i].second == currentDriverMetadata) {
60 setSelectedDriverIndex(i)
61 break
62 }
63 }
64
65 // If a user had installed a driver before the manager was implemented, this zips
66 // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
67 // be indexed and exported as expected.
68 if (selectedDriver == -1) {
69 val driverToSave =
70 File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
71 driverToSave.createNewFile()
72 FileUtil.zipFromInternalStorage(
73 File(GpuDriverHelper.driverInstallationPath!!),
74 GpuDriverHelper.driverInstallationPath!!,
75 BufferedOutputStream(driverToSave.outputStream())
76 )
77 drivers.add(Pair(driverToSave.path, currentDriverMetadata))
78 setSelectedDriverIndex(drivers.size - 1)
79 }
80
81 _driverList.value = drivers
82 _areDriversLoading.value = false
83 }
84 }
85 }
86
87 fun setSelectedDriverIndex(value: Int) {
88 if (selectedDriver != -1) {
89 previouslySelectedDriver = selectedDriver
90 }
91 selectedDriver = value
92 }
93
94 fun setNewDriverInstalled(value: Boolean) {
95 _newDriverInstalled.value = value
96 }
97
98 fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
99 val driverIndex = _driverList.value.indexOfFirst { it == driverData }
100 if (driverIndex == -1) {
101 setSelectedDriverIndex(_driverList.value.size)
102 _driverList.value.add(driverData)
103 _selectedDriverMetadata.value = driverData.second.name
104 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
105 } else {
106 setSelectedDriverIndex(driverIndex)
107 }
108 }
109
110 fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
111 _driverList.value.remove(driverData)
112 }
113
114 fun onCloseDriverManager() {
115 _isDeletingDrivers.value = true
116 viewModelScope.launch {
117 withContext(Dispatchers.IO) {
118 driversToDelete.forEach {
119 val driver = File(it)
120 if (driver.exists()) {
121 driver.delete()
122 }
123 }
124 driversToDelete.clear()
125 _isDeletingDrivers.value = false
126 }
127 }
128
129 if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
130 return
131 }
132
133 _isDriverReady.value = false
134 viewModelScope.launch {
135 withContext(Dispatchers.IO) {
136 if (selectedDriver == 0) {
137 GpuDriverHelper.installDefaultDriver()
138 setDriverReady()
139 return@withContext
140 }
141
142 val driverToInstall = File(driverList.value[selectedDriver].first)
143 if (driverToInstall.exists()) {
144 GpuDriverHelper.installCustomDriver(driverToInstall)
145 } else {
146 GpuDriverHelper.installDefaultDriver()
147 }
148 setDriverReady()
149 }
150 }
151 }
152
153 private fun setDriverReady() {
154 _isDriverReady.value = true
155 _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
156 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
157 }
158}
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..233aa4101 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
@@ -27,11 +29,10 @@ import androidx.navigation.fragment.NavHostFragment
27import androidx.navigation.ui.setupWithNavController 29import androidx.navigation.ui.setupWithNavController
28import androidx.preference.PreferenceManager 30import androidx.preference.PreferenceManager
29import com.google.android.material.color.MaterialColors 31import com.google.android.material.color.MaterialColors
30import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.navigation.NavigationBarView 32import com.google.android.material.navigation.NavigationBarView
33import kotlinx.coroutines.CoroutineScope
32import java.io.File 34import java.io.File
33import java.io.FilenameFilter 35import java.io.FilenameFilter
34import java.io.IOException
35import kotlinx.coroutines.Dispatchers 36import kotlinx.coroutines.Dispatchers
36import kotlinx.coroutines.launch 37import kotlinx.coroutines.launch
37import kotlinx.coroutines.withContext 38import kotlinx.coroutines.withContext
@@ -40,22 +41,40 @@ import org.yuzu.yuzu_emu.NativeLibrary
40import org.yuzu.yuzu_emu.R 41import org.yuzu.yuzu_emu.R
41import org.yuzu.yuzu_emu.activities.EmulationActivity 42import org.yuzu.yuzu_emu.activities.EmulationActivity
42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 43import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 44import org.yuzu.yuzu_emu.features.DocumentProvider
44import org.yuzu.yuzu_emu.features.settings.model.Settings 45import org.yuzu.yuzu_emu.features.settings.model.Settings
45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 46import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 47import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
48import org.yuzu.yuzu_emu.getPublicFilesDir
47import org.yuzu.yuzu_emu.model.GamesViewModel 49import org.yuzu.yuzu_emu.model.GamesViewModel
48import org.yuzu.yuzu_emu.model.HomeViewModel 50import org.yuzu.yuzu_emu.model.HomeViewModel
51import org.yuzu.yuzu_emu.model.TaskState
52import org.yuzu.yuzu_emu.model.TaskViewModel
49import org.yuzu.yuzu_emu.utils.* 53import org.yuzu.yuzu_emu.utils.*
54import java.io.BufferedInputStream
55import java.io.BufferedOutputStream
56import java.io.FileOutputStream
57import java.time.LocalDateTime
58import java.time.format.DateTimeFormatter
59import java.util.zip.ZipEntry
60import java.util.zip.ZipInputStream
50 61
51class MainActivity : AppCompatActivity(), ThemeProvider { 62class MainActivity : AppCompatActivity(), ThemeProvider {
52 private lateinit var binding: ActivityMainBinding 63 private lateinit var binding: ActivityMainBinding
53 64
54 private val homeViewModel: HomeViewModel by viewModels() 65 private val homeViewModel: HomeViewModel by viewModels()
55 private val gamesViewModel: GamesViewModel by viewModels() 66 private val gamesViewModel: GamesViewModel by viewModels()
67 private val taskViewModel: TaskViewModel by viewModels()
56 68
57 override var themeId: Int = 0 69 override var themeId: Int = 0
58 70
71 private val savesFolder
72 get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
73
74 // Get first subfolder in saves folder (should be the user folder)
75 val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
76 private var lastZipCreated: File? = null
77
59 override fun onCreate(savedInstanceState: Bundle?) { 78 override fun onCreate(savedInstanceState: Bundle?) {
60 val splashScreen = installSplashScreen() 79 val splashScreen = installSplashScreen()
61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } 80 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -307,6 +326,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
307 fun processKey(result: Uri): Boolean { 326 fun processKey(result: Uri): Boolean {
308 if (FileUtil.getExtension(result) != "keys") { 327 if (FileUtil.getExtension(result) != "keys") {
309 MessageDialogFragment.newInstance( 328 MessageDialogFragment.newInstance(
329 this,
310 titleId = R.string.reading_keys_failure, 330 titleId = R.string.reading_keys_failure,
311 descriptionId = R.string.install_prod_keys_failure_extension_description 331 descriptionId = R.string.install_prod_keys_failure_extension_description
312 ).show(supportFragmentManager, MessageDialogFragment.TAG) 332 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -320,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
320 340
321 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 341 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
322 if (FileUtil.copyUriToInternalStorage( 342 if (FileUtil.copyUriToInternalStorage(
323 applicationContext,
324 result, 343 result,
325 dstPath, 344 dstPath,
326 "prod.keys" 345 "prod.keys"
327 ) 346 ) != null
328 ) { 347 ) {
329 if (NativeLibrary.reloadKeys()) { 348 if (NativeLibrary.reloadKeys()) {
330 Toast.makeText( 349 Toast.makeText(
@@ -336,6 +355,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
336 return true 355 return true
337 } else { 356 } else {
338 MessageDialogFragment.newInstance( 357 MessageDialogFragment.newInstance(
358 this,
339 titleId = R.string.invalid_keys_error, 359 titleId = R.string.invalid_keys_error,
340 descriptionId = R.string.install_keys_failure_description, 360 descriptionId = R.string.install_keys_failure_description,
341 helpLinkId = R.string.dumping_keys_quickstart_link 361 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -371,11 +391,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
371 val task: () -> Any = { 391 val task: () -> Any = {
372 var messageToShow: Any 392 var messageToShow: Any
373 try { 393 try {
374 FileUtil.unzip(inputZip, cacheFirmwareDir) 394 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
375 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 395 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
376 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 396 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
377 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { 397 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
378 MessageDialogFragment.newInstance( 398 MessageDialogFragment.newInstance(
399 this,
379 titleId = R.string.firmware_installed_failure, 400 titleId = R.string.firmware_installed_failure,
380 descriptionId = R.string.firmware_installed_failure_description 401 descriptionId = R.string.firmware_installed_failure_description
381 ) 402 )
@@ -395,7 +416,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
395 IndeterminateProgressDialogFragment.newInstance( 416 IndeterminateProgressDialogFragment.newInstance(
396 this, 417 this,
397 R.string.firmware_installing, 418 R.string.firmware_installing,
398 task 419 task = task
399 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 420 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
400 } 421 }
401 422
@@ -407,6 +428,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
407 428
408 if (FileUtil.getExtension(result) != "bin") { 429 if (FileUtil.getExtension(result) != "bin") {
409 MessageDialogFragment.newInstance( 430 MessageDialogFragment.newInstance(
431 this,
410 titleId = R.string.reading_keys_failure, 432 titleId = R.string.reading_keys_failure,
411 descriptionId = R.string.install_amiibo_keys_failure_extension_description 433 descriptionId = R.string.install_amiibo_keys_failure_extension_description
412 ).show(supportFragmentManager, MessageDialogFragment.TAG) 434 ).show(supportFragmentManager, MessageDialogFragment.TAG)
@@ -420,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
420 442
421 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 443 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
422 if (FileUtil.copyUriToInternalStorage( 444 if (FileUtil.copyUriToInternalStorage(
423 applicationContext,
424 result, 445 result,
425 dstPath, 446 dstPath,
426 "key_retail.bin" 447 "key_retail.bin"
427 ) 448 ) != null
428 ) { 449 ) {
429 if (NativeLibrary.reloadKeys()) { 450 if (NativeLibrary.reloadKeys()) {
430 Toast.makeText( 451 Toast.makeText(
@@ -434,6 +455,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
434 ).show() 455 ).show()
435 } else { 456 } else {
436 MessageDialogFragment.newInstance( 457 MessageDialogFragment.newInstance(
458 this,
437 titleId = R.string.invalid_keys_error, 459 titleId = R.string.invalid_keys_error,
438 descriptionId = R.string.install_keys_failure_description, 460 descriptionId = R.string.install_keys_failure_description,
439 helpLinkId = R.string.dumping_keys_quickstart_link 461 helpLinkId = R.string.dumping_keys_quickstart_link
@@ -442,66 +464,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
442 } 464 }
443 } 465 }
444 466
445 val getDriver =
446 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
447 if (result == null) {
448 return@registerForActivityResult
449 }
450
451 val takeFlags =
452 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
453 contentResolver.takePersistableUriPermission(
454 result,
455 takeFlags
456 )
457
458 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
459 progressBinding.progressBar.isIndeterminate = true
460 val installationDialog = MaterialAlertDialogBuilder(this)
461 .setTitle(R.string.installing_driver)
462 .setView(progressBinding.root)
463 .show()
464
465 lifecycleScope.launch {
466 withContext(Dispatchers.IO) {
467 // Ignore file exceptions when a user selects an invalid zip
468 try {
469 GpuDriverHelper.installCustomDriver(applicationContext, result)
470 } catch (_: IOException) {
471 }
472
473 withContext(Dispatchers.Main) {
474 installationDialog.dismiss()
475
476 val driverName = GpuDriverHelper.customDriverName
477 if (driverName != null) {
478 Toast.makeText(
479 applicationContext,
480 getString(
481 R.string.select_gpu_driver_install_success,
482 driverName
483 ),
484 Toast.LENGTH_SHORT
485 ).show()
486 } else {
487 Toast.makeText(
488 applicationContext,
489 R.string.select_gpu_driver_error,
490 Toast.LENGTH_LONG
491 ).show()
492 }
493 }
494 }
495 }
496 }
497
498 val installGameUpdate = registerForActivityResult( 467 val installGameUpdate = registerForActivityResult(
499 ActivityResultContracts.OpenMultipleDocuments() 468 ActivityResultContracts.OpenMultipleDocuments()
500 ) { documents: List<Uri> -> 469 ) { documents: List<Uri> ->
501 if (documents.isNotEmpty()) { 470 if (documents.isNotEmpty()) {
502 IndeterminateProgressDialogFragment.newInstance( 471 IndeterminateProgressDialogFragment.newInstance(
503 this@MainActivity, 472 this@MainActivity,
504 R.string.install_game_content 473 R.string.installing_game_content
505 ) { 474 ) {
506 var installSuccess = 0 475 var installSuccess = 0
507 var installOverwrite = 0 476 var installOverwrite = 0
@@ -509,7 +478,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
509 var errorExtension = 0 478 var errorExtension = 0
510 var errorOther = 0 479 var errorOther = 0
511 documents.forEach { 480 documents.forEach {
512 when (NativeLibrary.installFileToNand(it.toString())) { 481 when (
482 NativeLibrary.installFileToNand(
483 it.toString(),
484 FileUtil.getExtension(it)
485 )
486 ) {
513 NativeLibrary.InstallFileToNandResult.Success -> { 487 NativeLibrary.InstallFileToNandResult.Success -> {
514 installSuccess += 1 488 installSuccess += 1
515 } 489 }
@@ -583,12 +557,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
583 installResult.append(separator) 557 installResult.append(separator)
584 } 558 }
585 return@newInstance MessageDialogFragment.newInstance( 559 return@newInstance MessageDialogFragment.newInstance(
560 this,
586 titleId = R.string.install_game_content_failure, 561 titleId = R.string.install_game_content_failure,
587 descriptionString = installResult.toString().trim(), 562 descriptionString = installResult.toString().trim(),
588 helpLinkId = R.string.install_game_content_help_link 563 helpLinkId = R.string.install_game_content_help_link
589 ) 564 )
590 } else { 565 } else {
591 return@newInstance MessageDialogFragment.newInstance( 566 return@newInstance MessageDialogFragment.newInstance(
567 this,
592 titleId = R.string.install_game_content_success, 568 titleId = R.string.install_game_content_success,
593 descriptionString = installResult.toString().trim() 569 descriptionString = installResult.toString().trim()
594 ) 570 )
@@ -596,4 +572,228 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
596 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 572 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
597 } 573 }
598 } 574 }
575
576 val exportUserData = registerForActivityResult(
577 ActivityResultContracts.CreateDocument("application/zip")
578 ) { result ->
579 if (result == null) {
580 return@registerForActivityResult
581 }
582
583 IndeterminateProgressDialogFragment.newInstance(
584 this,
585 R.string.exporting_user_data,
586 true
587 ) {
588 val zipResult = FileUtil.zipFromInternalStorage(
589 File(DirectoryInitialization.userDirectory!!),
590 DirectoryInitialization.userDirectory!!,
591 BufferedOutputStream(contentResolver.openOutputStream(result)),
592 taskViewModel.cancelled
593 )
594 return@newInstance when (zipResult) {
595 TaskState.Completed -> getString(R.string.user_data_export_success)
596 TaskState.Failed -> R.string.export_failed
597 TaskState.Cancelled -> R.string.user_data_export_cancelled
598 }
599 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
600 }
601
602 val importUserData =
603 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
604 if (result == null) {
605 return@registerForActivityResult
606 }
607
608 IndeterminateProgressDialogFragment.newInstance(
609 this,
610 R.string.importing_user_data
611 ) {
612 val checkStream =
613 ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
614 var isYuzuBackup = false
615 checkStream.use { stream ->
616 var ze: ZipEntry? = null
617 while (stream.nextEntry?.also { ze = it } != null) {
618 val itemName = ze!!.name.trim()
619 if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
620 isYuzuBackup = true
621 return@use
622 }
623 }
624 }
625 if (!isYuzuBackup) {
626 return@newInstance MessageDialogFragment.newInstance(
627 this,
628 titleId = R.string.invalid_yuzu_backup,
629 descriptionId = R.string.user_data_import_failed_description
630 )
631 }
632
633 // Clear existing user data
634 File(DirectoryInitialization.userDirectory!!).deleteRecursively()
635
636 // Copy archive to internal storage
637 try {
638 FileUtil.unzipToInternalStorage(
639 BufferedInputStream(contentResolver.openInputStream(result)),
640 File(DirectoryInitialization.userDirectory!!)
641 )
642 } catch (e: Exception) {
643 return@newInstance MessageDialogFragment.newInstance(
644 this,
645 titleId = R.string.import_failed,
646 descriptionId = R.string.user_data_import_failed_description
647 )
648 }
649
650 // Reinitialize relevant data
651 NativeLibrary.initializeEmulation()
652 gamesViewModel.reloadGames(false)
653
654 return@newInstance getString(R.string.user_data_import_success)
655 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
656 }
657
658 /**
659 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
660 * @return true if the zip file is successfully created, false otherwise.
661 */
662 private fun zipSave(): Boolean {
663 try {
664 val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
665 tempFolder.mkdirs()
666 val saveFolder = File(savesFolderRoot)
667 val outputZipFile = File(
668 tempFolder,
669 "yuzu saves - ${
670 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
671 }.zip"
672 )
673 outputZipFile.createNewFile()
674 val result = FileUtil.zipFromInternalStorage(
675 saveFolder,
676 savesFolderRoot,
677 BufferedOutputStream(FileOutputStream(outputZipFile))
678 )
679 if (result == TaskState.Failed) {
680 return false
681 }
682 lastZipCreated = outputZipFile
683 } catch (e: Exception) {
684 return false
685 }
686 return true
687 }
688
689 /**
690 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
691 */
692 fun exportSave() {
693 CoroutineScope(Dispatchers.IO).launch {
694 val wasZipCreated = zipSave()
695 val lastZipFile = lastZipCreated
696 if (!wasZipCreated || lastZipFile == null) {
697 withContext(Dispatchers.Main) {
698 Toast.makeText(
699 this@MainActivity,
700 getString(R.string.export_save_failed),
701 Toast.LENGTH_LONG
702 ).show()
703 }
704 return@launch
705 }
706
707 withContext(Dispatchers.Main) {
708 val file = DocumentFile.fromSingleUri(
709 this@MainActivity,
710 DocumentsContract.buildDocumentUri(
711 DocumentProvider.AUTHORITY,
712 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
713 )
714 )!!
715 val intent = Intent(Intent.ACTION_SEND)
716 .setDataAndType(file.uri, "application/zip")
717 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
718 .putExtra(Intent.EXTRA_STREAM, file.uri)
719 startForResultExportSave.launch(
720 Intent.createChooser(
721 intent,
722 getString(R.string.share_save_file)
723 )
724 )
725 }
726 }
727 }
728
729 private val startForResultExportSave =
730 registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
731 File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
732 }
733
734 val importSaves =
735 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
736 if (result == null) {
737 return@registerForActivityResult
738 }
739
740 NativeLibrary.initializeEmptyUserDirectory()
741
742 val inputZip = contentResolver.openInputStream(result)
743 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
744 var validZip = false
745 val savesFolder = File(savesFolderRoot)
746 val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/")
747 cacheSaveDir.mkdir()
748
749 if (inputZip == null) {
750 Toast.makeText(
751 applicationContext,
752 getString(R.string.fatal_error),
753 Toast.LENGTH_LONG
754 ).show()
755 return@registerForActivityResult
756 }
757
758 val filterTitleId =
759 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
760
761 try {
762 CoroutineScope(Dispatchers.IO).launch {
763 FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
764 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
765 File(savesFolder, savePath).deleteRecursively()
766 File(cacheSaveDir, savePath).copyRecursively(
767 File(savesFolder, savePath),
768 true
769 )
770 validZip = true
771 }
772
773 withContext(Dispatchers.Main) {
774 if (!validZip) {
775 MessageDialogFragment.newInstance(
776 this@MainActivity,
777 titleId = R.string.save_file_invalid_zip_structure,
778 descriptionId = R.string.save_file_invalid_zip_structure_description
779 ).show(supportFragmentManager, MessageDialogFragment.TAG)
780 return@withContext
781 }
782 Toast.makeText(
783 applicationContext,
784 getString(R.string.save_file_imported_success),
785 Toast.LENGTH_LONG
786 ).show()
787 }
788
789 cacheSaveDir.deleteRecursively()
790 }
791 } catch (e: Exception) {
792 Toast.makeText(
793 applicationContext,
794 getString(R.string.fatal_error),
795 Toast.LENGTH_LONG
796 ).show()
797 }
798 }
599} 799}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index cf226ad94..eafcf9e42 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -7,7 +7,6 @@ import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import java.io.File 8import java.io.File
9import java.util.* 9import java.util.*
10import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 10import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12 11
13class DocumentsTree { 12class DocumentsTree {
@@ -22,7 +21,7 @@ class DocumentsTree {
22 21
23 fun openContentUri(filepath: String, openMode: String?): Int { 22 fun openContentUri(filepath: String, openMode: String?): Int {
24 val node = resolvePath(filepath) ?: return -1 23 val node = resolvePath(filepath) ?: return -1
25 return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode) 24 return FileUtil.openContentUri(node.uri.toString(), openMode)
26 } 25 }
27 26
28 fun getFileSize(filepath: String): Long { 27 fun getFileSize(filepath: String): Long {
@@ -30,7 +29,7 @@ class DocumentsTree {
30 return if (node == null || node.isDirectory) { 29 return if (node == null || node.isDirectory) {
31 0 30 0
32 } else { 31 } else {
33 FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) 32 FileUtil.getFileSize(node.uri.toString())
34 } 33 }
35 } 34 }
36 35
@@ -67,7 +66,7 @@ class DocumentsTree {
67 * @param parent parent node of this level 66 * @param parent parent node of this level
68 */ 67 */
69 private fun structTree(parent: DocumentsNode) { 68 private fun structTree(parent: DocumentsNode) {
70 val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!) 69 val documents = FileUtil.listFiles(parent.uri!!)
71 for (document in documents) { 70 for (document in documents) {
72 val node = DocumentsNode(document) 71 val node = DocumentsNode(document)
73 node.parent = parent 72 node.parent = parent
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..5ee74a52c 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
@@ -3,14 +3,13 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.database.Cursor 6import android.database.Cursor
8import android.net.Uri 7import android.net.Uri
9import android.provider.DocumentsContract 8import android.provider.DocumentsContract
10import androidx.documentfile.provider.DocumentFile 9import androidx.documentfile.provider.DocumentFile
10import kotlinx.coroutines.flow.StateFlow
11import java.io.BufferedInputStream 11import java.io.BufferedInputStream
12import java.io.File 12import java.io.File
13import java.io.FileOutputStream
14import java.io.IOException 13import java.io.IOException
15import java.io.InputStream 14import java.io.InputStream
16import java.net.URLDecoder 15import java.net.URLDecoder
@@ -18,6 +17,11 @@ import java.util.zip.ZipEntry
18import java.util.zip.ZipInputStream 17import java.util.zip.ZipInputStream
19import org.yuzu.yuzu_emu.YuzuApplication 18import org.yuzu.yuzu_emu.YuzuApplication
20import org.yuzu.yuzu_emu.model.MinimalDocumentFile 19import org.yuzu.yuzu_emu.model.MinimalDocumentFile
20import org.yuzu.yuzu_emu.model.TaskState
21import java.io.BufferedOutputStream
22import java.lang.NullPointerException
23import java.nio.charset.StandardCharsets
24import java.util.zip.ZipOutputStream
21 25
22object FileUtil { 26object FileUtil {
23 const val PATH_TREE = "tree" 27 const val PATH_TREE = "tree"
@@ -25,6 +29,8 @@ object FileUtil {
25 const val APPLICATION_OCTET_STREAM = "application/octet-stream" 29 const val APPLICATION_OCTET_STREAM = "application/octet-stream"
26 const val TEXT_PLAIN = "text/plain" 30 const val TEXT_PLAIN = "text/plain"
27 31
32 private val context get() = YuzuApplication.appContext
33
28 /** 34 /**
29 * Create a file from directory with filename. 35 * Create a file from directory with filename.
30 * @param context Application context 36 * @param context Application context
@@ -32,11 +38,11 @@ object FileUtil {
32 * @param filename file display name. 38 * @param filename file display name.
33 * @return boolean 39 * @return boolean
34 */ 40 */
35 fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? { 41 fun createFile(directory: String?, filename: String): DocumentFile? {
36 var decodedFilename = filename 42 var decodedFilename = filename
37 try { 43 try {
38 val directoryUri = Uri.parse(directory) 44 val directoryUri = Uri.parse(directory)
39 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 45 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
40 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD) 46 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
41 var mimeType = APPLICATION_OCTET_STREAM 47 var mimeType = APPLICATION_OCTET_STREAM
42 if (decodedFilename.endsWith(".txt")) { 48 if (decodedFilename.endsWith(".txt")) {
@@ -52,16 +58,15 @@ object FileUtil {
52 58
53 /** 59 /**
54 * Create a directory from directory with filename. 60 * Create a directory from directory with filename.
55 * @param context Application context
56 * @param directory parent path for directory. 61 * @param directory parent path for directory.
57 * @param directoryName directory display name. 62 * @param directoryName directory display name.
58 * @return boolean 63 * @return boolean
59 */ 64 */
60 fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? { 65 fun createDir(directory: String?, directoryName: String?): DocumentFile? {
61 var decodedDirectoryName = directoryName 66 var decodedDirectoryName = directoryName
62 try { 67 try {
63 val directoryUri = Uri.parse(directory) 68 val directoryUri = Uri.parse(directory)
64 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 69 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
65 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD) 70 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
66 val isExist = parent.findFile(decodedDirectoryName) 71 val isExist = parent.findFile(decodedDirectoryName)
67 return isExist ?: parent.createDirectory(decodedDirectoryName) 72 return isExist ?: parent.createDirectory(decodedDirectoryName)
@@ -73,13 +78,12 @@ object FileUtil {
73 78
74 /** 79 /**
75 * Open content uri and return file descriptor to JNI. 80 * Open content uri and return file descriptor to JNI.
76 * @param context Application context
77 * @param path Native content uri path 81 * @param path Native content uri path
78 * @param openMode will be one of "r", "r", "rw", "wa", "rwa" 82 * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
79 * @return file descriptor 83 * @return file descriptor
80 */ 84 */
81 @JvmStatic 85 @JvmStatic
82 fun openContentUri(context: Context, path: String, openMode: String?): Int { 86 fun openContentUri(path: String, openMode: String?): Int {
83 try { 87 try {
84 val uri = Uri.parse(path) 88 val uri = Uri.parse(path)
85 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) 89 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@@ -99,11 +103,10 @@ object FileUtil {
99 /** 103 /**
100 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow 104 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
101 * This function will be faster than DoucmentFile.listFiles 105 * This function will be faster than DoucmentFile.listFiles
102 * @param context Application context
103 * @param uri Directory uri. 106 * @param uri Directory uri.
104 * @return CheapDocument lists. 107 * @return CheapDocument lists.
105 */ 108 */
106 fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> { 109 fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
107 val resolver = context.contentResolver 110 val resolver = context.contentResolver
108 val columns = arrayOf( 111 val columns = arrayOf(
109 DocumentsContract.Document.COLUMN_DOCUMENT_ID, 112 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -141,7 +144,7 @@ object FileUtil {
141 * @param path Native content uri path 144 * @param path Native content uri path
142 * @return bool 145 * @return bool
143 */ 146 */
144 fun exists(context: Context, path: String?): Boolean { 147 fun exists(path: String?): Boolean {
145 var c: Cursor? = null 148 var c: Cursor? = null
146 try { 149 try {
147 val mUri = Uri.parse(path) 150 val mUri = Uri.parse(path)
@@ -161,7 +164,7 @@ object FileUtil {
161 * @param path content uri path 164 * @param path content uri path
162 * @return bool 165 * @return bool
163 */ 166 */
164 fun isDirectory(context: Context, path: String): Boolean { 167 fun isDirectory(path: String): Boolean {
165 val resolver = context.contentResolver 168 val resolver = context.contentResolver
166 val columns = arrayOf( 169 val columns = arrayOf(
167 DocumentsContract.Document.COLUMN_MIME_TYPE 170 DocumentsContract.Document.COLUMN_MIME_TYPE
@@ -206,10 +209,10 @@ object FileUtil {
206 return filename 209 return filename
207 } 210 }
208 211
209 fun getFilesName(context: Context, path: String): Array<String> { 212 fun getFilesName(path: String): Array<String> {
210 val uri = Uri.parse(path) 213 val uri = Uri.parse(path)
211 val files: MutableList<String> = ArrayList() 214 val files: MutableList<String> = ArrayList()
212 for (file in listFiles(context, uri)) { 215 for (file in listFiles(uri)) {
213 files.add(file.filename) 216 files.add(file.filename)
214 } 217 }
215 return files.toTypedArray() 218 return files.toTypedArray()
@@ -221,7 +224,7 @@ object FileUtil {
221 * @return long file size 224 * @return long file size
222 */ 225 */
223 @JvmStatic 226 @JvmStatic
224 fun getFileSize(context: Context, path: String): Long { 227 fun getFileSize(path: String): Long {
225 val resolver = context.contentResolver 228 val resolver = context.contentResolver
226 val columns = arrayOf( 229 val columns = arrayOf(
227 DocumentsContract.Document.COLUMN_SIZE 230 DocumentsContract.Document.COLUMN_SIZE
@@ -241,71 +244,100 @@ object FileUtil {
241 return size 244 return size
242 } 245 }
243 246
247 /**
248 * Creates an input stream with a given [Uri] and copies its data to the given path. This will
249 * overwrite any pre-existing files.
250 *
251 * @param sourceUri The [Uri] to copy data from
252 * @param destinationParentPath Destination directory
253 * @param destinationFilename Optionally renames the file once copied
254 */
244 fun copyUriToInternalStorage( 255 fun copyUriToInternalStorage(
245 context: Context, 256 sourceUri: Uri,
246 sourceUri: Uri?,
247 destinationParentPath: String, 257 destinationParentPath: String,
248 destinationFilename: String 258 destinationFilename: String = ""
249 ): Boolean { 259 ): File? =
250 var input: InputStream? = null
251 var output: FileOutputStream? = null
252 try { 260 try {
253 input = context.contentResolver.openInputStream(sourceUri!!) 261 val fileName =
254 output = FileOutputStream("$destinationParentPath/$destinationFilename") 262 if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
255 val buffer = ByteArray(1024) 263 val inputStream = context.contentResolver.openInputStream(sourceUri)!!
256 var len: Int 264
257 while (input!!.read(buffer).also { len = it } != -1) { 265 val destinationFile = File("$destinationParentPath$fileName")
258 output.write(buffer, 0, len) 266 if (destinationFile.exists()) {
259 } 267 destinationFile.delete()
260 output.flush()
261 return true
262 } catch (e: Exception) {
263 Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
264 } finally {
265 if (input != null) {
266 try {
267 input.close()
268 } catch (e: IOException) {
269 Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
270 }
271 } 268 }
272 if (output != null) { 269
273 try { 270 destinationFile.outputStream().use { fos ->
274 output.close() 271 inputStream.use { it.copyTo(fos) }
275 } catch (e: IOException) {
276 Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
277 }
278 } 272 }
273 destinationFile
274 } catch (e: IOException) {
275 null
276 } catch (e: NullPointerException) {
277 null
279 } 278 }
280 return false
281 }
282 279
283 /** 280 /**
284 * Extracts the given zip file into the given directory. 281 * Extracts the given zip file into the given directory.
285 * @exception IOException if the file was being created outside of the target directory
286 */ 282 */
287 @Throws(SecurityException::class) 283 @Throws(SecurityException::class)
288 fun unzip(zipStream: InputStream, destDir: File): Boolean { 284 fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
289 ZipInputStream(BufferedInputStream(zipStream)).use { zis -> 285 ZipInputStream(zipStream).use { zis ->
290 var entry: ZipEntry? = zis.nextEntry 286 var entry: ZipEntry? = zis.nextEntry
291 while (entry != null) { 287 while (entry != null) {
292 val entryName = entry.name 288 val newFile = File(destDir, entry.name)
293 val entryFile = File(destDir, entryName) 289 val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
294 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) { 290
295 throw SecurityException("Entry is outside of the target dir: " + entryFile.name) 291 if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
292 throw SecurityException("Zip file attempted path traversal! ${entry.name}")
293 }
294
295 if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
296 throw IOException("Failed to create directory $destinationDirectory")
296 } 297 }
297 if (entry.isDirectory) { 298
298 entryFile.mkdirs() 299 if (!entry.isDirectory) {
299 } else { 300 newFile.outputStream().use { fos -> zis.copyTo(fos) }
300 entryFile.parentFile?.mkdirs()
301 entryFile.createNewFile()
302 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
303 } 301 }
304 entry = zis.nextEntry 302 entry = zis.nextEntry
305 } 303 }
306 } 304 }
305 }
307 306
308 return true 307 /**
308 * Creates a zip file from a directory within internal storage
309 * @param inputFile File representation of the item that will be zipped
310 * @param rootDir Directory containing the inputFile
311 * @param outputStream Stream where the zip file will be output
312 */
313 fun zipFromInternalStorage(
314 inputFile: File,
315 rootDir: String,
316 outputStream: BufferedOutputStream,
317 cancelled: StateFlow<Boolean>? = null
318 ): TaskState {
319 try {
320 ZipOutputStream(outputStream).use { zos ->
321 inputFile.walkTopDown().forEach { file ->
322 if (cancelled?.value == true) {
323 return TaskState.Cancelled
324 }
325
326 if (!file.isDirectory) {
327 val entryName =
328 file.absolutePath.removePrefix(rootDir).removePrefix("/")
329 val entry = ZipEntry(entryName)
330 zos.putNextEntry(entry)
331 if (file.isFile) {
332 file.inputStream().use { fis -> fis.copyTo(zos) }
333 }
334 }
335 }
336 }
337 } catch (e: Exception) {
338 return TaskState.Failed
339 }
340 return TaskState.Completed
309 } 341 }
310 342
311 fun isRootTreeUri(uri: Uri): Boolean { 343 fun isRootTreeUri(uri: Uri): Boolean {
@@ -329,4 +361,12 @@ object FileUtil {
329 return fileName.substring(fileName.lastIndexOf(".") + 1) 361 return fileName.substring(fileName.lastIndexOf(".") + 1)
330 .lowercase() 362 .lowercase()
331 } 363 }
364
365 @Throws(IOException::class)
366 fun getStringFromFile(file: File): String =
367 String(file.readBytes(), StandardCharsets.UTF_8)
368
369 @Throws(IOException::class)
370 fun getStringFromInputStream(stream: InputStream): String =
371 String(stream.readBytes(), StandardCharsets.UTF_8)
332} 372}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index e0ee29c9b..9001ca9ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -30,7 +30,7 @@ object GameHelper {
30 // Ensure keys are loaded so that ROM metadata can be decrypted. 30 // Ensure keys are loaded so that ROM metadata can be decrypted.
31 NativeLibrary.reloadKeys() 31 NativeLibrary.reloadKeys()
32 32
33 addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3) 33 addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
34 34
35 // Cache list of games found on disk 35 // Cache list of games found on disk
36 val serializedGames = mutableSetOf<String>() 36 val serializedGames = mutableSetOf<String>()
@@ -58,7 +58,7 @@ object GameHelper {
58 if (it.isDirectory) { 58 if (it.isDirectory) {
59 addGamesRecursive( 59 addGamesRecursive(
60 games, 60 games,
61 FileUtil.listFiles(YuzuApplication.appContext, it.uri), 61 FileUtil.listFiles(it.uri),
62 depth - 1 62 depth - 1
63 ) 63 )
64 } else { 64 } else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 1d4695a2a..f6882ce6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -3,64 +3,33 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.net.Uri 6import android.net.Uri
7import android.os.Build
8import java.io.BufferedInputStream 8import java.io.BufferedInputStream
9import java.io.File 9import java.io.File
10import java.io.FileInputStream
11import java.io.FileOutputStream
12import java.io.IOException 10import java.io.IOException
13import java.util.zip.ZipInputStream
14import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
15import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage 12import org.yuzu.yuzu_emu.YuzuApplication
13import java.util.zip.ZipException
14import java.util.zip.ZipFile
16 15
17object GpuDriverHelper { 16object GpuDriverHelper {
18 private const val META_JSON_FILENAME = "meta.json" 17 private const val META_JSON_FILENAME = "meta.json"
19 private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
20 private var fileRedirectionPath: String? = null 18 private var fileRedirectionPath: String? = null
21 private var driverInstallationPath: String? = null 19 var driverInstallationPath: String? = null
22 private var hookLibPath: String? = null 20 private var hookLibPath: String? = null
23 21
24 @Throws(IOException::class) 22 val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
25 private fun unzip(zipFilePath: String, destDir: String) {
26 val dir = File(destDir)
27
28 // Create output directory if it doesn't exist
29 if (!dir.exists()) dir.mkdirs()
30
31 // Unpack the files.
32 val inputStream = FileInputStream(zipFilePath)
33 val zis = ZipInputStream(BufferedInputStream(inputStream))
34 val buffer = ByteArray(1024)
35 var ze = zis.nextEntry
36 while (ze != null) {
37 val newFile = File(destDir, ze.name)
38 val canonicalPath = newFile.canonicalPath
39 if (!canonicalPath.startsWith(destDir + ze.name)) {
40 throw SecurityException("Zip file attempted path traversal! " + ze.name)
41 }
42
43 newFile.parentFile!!.mkdirs()
44 val fos = FileOutputStream(newFile)
45 var len: Int
46 while (zis.read(buffer).also { len = it } > 0) {
47 fos.write(buffer, 0, len)
48 }
49 fos.close()
50 zis.closeEntry()
51 ze = zis.nextEntry
52 }
53 zis.closeEntry()
54 }
55 23
56 fun initializeDriverParameters(context: Context) { 24 fun initializeDriverParameters() {
57 try { 25 try {
58 // Initialize the file redirection directory. 26 // Initialize the file redirection directory.
59 fileRedirectionPath = 27 fileRedirectionPath = YuzuApplication.appContext
60 context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/" 28 .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
61 29
62 // Initialize the driver installation directory. 30 // Initialize the driver installation directory.
63 driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/" 31 driverInstallationPath = YuzuApplication.appContext
32 .filesDir.canonicalPath + "/gpu_driver/"
64 } catch (e: IOException) { 33 } catch (e: IOException) {
65 throw RuntimeException(e) 34 throw RuntimeException(e)
66 } 35 }
@@ -69,68 +38,169 @@ object GpuDriverHelper {
69 initializeDirectories() 38 initializeDirectories()
70 39
71 // Initialize hook libraries directory. 40 // Initialize hook libraries directory.
72 hookLibPath = context.applicationInfo.nativeLibraryDir + "/" 41 hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
73 42
74 // Initialize GPU driver. 43 // Initialize GPU driver.
75 NativeLibrary.initializeGpuDriver( 44 NativeLibrary.initializeGpuDriver(
76 hookLibPath, 45 hookLibPath,
77 driverInstallationPath, 46 driverInstallationPath,
78 customDriverLibraryName, 47 customDriverData.libraryName,
79 fileRedirectionPath 48 fileRedirectionPath
80 ) 49 )
81 } 50 }
82 51
83 fun installDefaultDriver(context: Context) { 52 fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
53 val driverZips = File(driverStoragePath).listFiles()
54 val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
55 driverZips
56 ?.mapNotNull {
57 val metadata = getMetadataFromZip(it)
58 metadata.name?.let { _ -> Pair(it.path, metadata) }
59 }
60 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
61 ?.distinct()
62 ?.toMutableList() ?: mutableListOf()
63
64 // TODO: Get system driver information
65 drivers.add(0, Pair("", GpuDriverMetadata()))
66 return drivers
67 }
68
69 fun installDefaultDriver() {
84 // Removing the installed driver will result in the backend using the default system driver. 70 // Removing the installed driver will result in the backend using the default system driver.
85 val driverInstallationDir = File(driverInstallationPath!!) 71 File(driverInstallationPath!!).deleteRecursively()
86 deleteRecursive(driverInstallationDir) 72 initializeDriverParameters()
87 initializeDriverParameters(context) 73 }
74
75 fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
76 // Ensure we have directories.
77 initializeDirectories()
78
79 // Copy the zip file URI to user data
80 val copiedFile =
81 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
82
83 // Validate driver
84 val metadata = getMetadataFromZip(copiedFile)
85 if (metadata.name == null) {
86 copiedFile.delete()
87 return false
88 }
89
90 if (metadata.minApi > Build.VERSION.SDK_INT) {
91 copiedFile.delete()
92 return false
93 }
94 return true
88 } 95 }
89 96
90 fun installCustomDriver(context: Context, driverPathUri: Uri?) { 97 /**
98 * Copies driver zip into user data directory so that it can be exported along with
99 * other user data and also unzipped into the installation directory
100 */
101 fun installCustomDriver(driverUri: Uri): Boolean {
91 // Revert to system default in the event the specified driver is bad. 102 // Revert to system default in the event the specified driver is bad.
92 installDefaultDriver(context) 103 installDefaultDriver()
93 104
94 // Ensure we have directories. 105 // Ensure we have directories.
95 initializeDirectories() 106 initializeDirectories()
96 107
97 // Copy the zip file URI into our private storage. 108 // Copy the zip file URI to user data
98 copyUriToInternalStorage( 109 val copiedFile =
99 context, 110 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
100 driverPathUri, 111
101 driverInstallationPath!!, 112 // Validate driver
102 DRIVER_INTERNAL_FILENAME 113 val metadata = getMetadataFromZip(copiedFile)
103 ) 114 if (metadata.name == null) {
115 copiedFile.delete()
116 return false
117 }
118
119 if (metadata.minApi > Build.VERSION.SDK_INT) {
120 copiedFile.delete()
121 return false
122 }
104 123
105 // Unzip the driver. 124 // Unzip the driver.
106 try { 125 try {
107 unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!) 126 FileUtil.unzipToInternalStorage(
127 BufferedInputStream(copiedFile.inputStream()),
128 File(driverInstallationPath!!)
129 )
108 } catch (e: SecurityException) { 130 } catch (e: SecurityException) {
109 return 131 return false
110 } 132 }
111 133
112 // Initialize the driver parameters. 134 // Initialize the driver parameters.
113 initializeDriverParameters(context) 135 initializeDriverParameters()
136
137 return true
114 } 138 }
115 139
116 external fun supportsCustomDriverLoading(): Boolean 140 /**
141 * Unzips driver into installation directory
142 */
143 fun installCustomDriver(driver: File): Boolean {
144 // Revert to system default in the event the specified driver is bad.
145 installDefaultDriver()
117 146
118 // Parse the custom driver metadata to retrieve the name. 147 // Ensure we have directories.
119 val customDriverName: String? 148 initializeDirectories()
120 get() { 149
121 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 150 // Validate driver
122 return metadata.name 151 val metadata = getMetadataFromZip(driver)
152 if (metadata.name == null) {
153 driver.delete()
154 return false
123 } 155 }
124 156
125 // Parse the custom driver metadata to retrieve the library name. 157 // Unzip the driver to the private installation directory
126 private val customDriverLibraryName: String? 158 try {
127 get() { 159 FileUtil.unzipToInternalStorage(
128 // Parse the custom driver metadata to retrieve the library name. 160 BufferedInputStream(driver.inputStream()),
129 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 161 File(driverInstallationPath!!)
130 return metadata.libraryName 162 )
163 } catch (e: SecurityException) {
164 return false
131 } 165 }
132 166
133 private fun initializeDirectories() { 167 // Initialize the driver parameters.
168 initializeDriverParameters()
169
170 return true
171 }
172
173 /**
174 * Takes in a zip file and reads the meta.json file for presentation to the UI
175 *
176 * @param driver Zip containing driver and meta.json file
177 * @return A non-null [GpuDriverMetadata] instance that may have null members
178 */
179 fun getMetadataFromZip(driver: File): GpuDriverMetadata {
180 try {
181 ZipFile(driver).use { zf ->
182 val entries = zf.entries()
183 while (entries.hasMoreElements()) {
184 val entry = entries.nextElement()
185 if (!entry.isDirectory && entry.name.lowercase().contains(".json")) {
186 zf.getInputStream(entry).use {
187 return GpuDriverMetadata(it, entry.size)
188 }
189 }
190 }
191 }
192 } catch (_: ZipException) {
193 }
194 return GpuDriverMetadata()
195 }
196
197 external fun supportsCustomDriverLoading(): Boolean
198
199 // Parse the custom driver metadata to retrieve the name.
200 val customDriverData: GpuDriverMetadata
201 get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
202
203 fun initializeDirectories() {
134 // Ensure the file redirection directory exists. 204 // Ensure the file redirection directory exists.
135 val fileRedirectionDir = File(fileRedirectionPath!!) 205 val fileRedirectionDir = File(fileRedirectionPath!!)
136 if (!fileRedirectionDir.exists()) { 206 if (!fileRedirectionDir.exists()) {
@@ -141,14 +211,10 @@ object GpuDriverHelper {
141 if (!driverInstallationDir.exists()) { 211 if (!driverInstallationDir.exists()) {
142 driverInstallationDir.mkdirs() 212 driverInstallationDir.mkdirs()
143 } 213 }
144 } 214 // Ensure the driver storage directory exists
145 215 val driverStorageDirectory = File(driverStoragePath)
146 private fun deleteRecursive(fileOrDirectory: File) { 216 if (!driverStorageDirectory.exists()) {
147 if (fileOrDirectory.isDirectory) { 217 driverStorageDirectory.mkdirs()
148 for (child in fileOrDirectory.listFiles()!!) {
149 deleteRecursive(child)
150 }
151 } 218 }
152 fileOrDirectory.delete()
153 } 219 }
154} 220}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
index a4e64070a..511a4171a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -4,29 +4,29 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import java.io.IOException 6import java.io.IOException
7import java.nio.charset.StandardCharsets
8import java.nio.file.Files
9import java.nio.file.Paths
10import org.json.JSONException 7import org.json.JSONException
11import org.json.JSONObject 8import org.json.JSONObject
9import java.io.File
10import java.io.InputStream
12 11
13class GpuDriverMetadata(metadataFilePath: String) { 12class GpuDriverMetadata {
14 var name: String? = null 13 /**
15 var description: String? = null 14 * Tries to get driver metadata information from a meta.json [File]
16 var author: String? = null 15 *
17 var vendor: String? = null 16 * @param metadataFile meta.json file provided with a GPU driver
18 var driverVersion: String? = null 17 */
19 var minApi = 0 18 constructor(metadataFile: File) {
20 var libraryName: String? = null 19 if (metadataFile.length() > MAX_META_SIZE_BYTES) {
20 return
21 }
21 22
22 init {
23 try { 23 try {
24 val json = JSONObject(getStringFromFile(metadataFilePath)) 24 val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
25 name = json.getString("name") 25 name = json.getString("name")
26 description = json.getString("description") 26 description = json.getString("description")
27 author = json.getString("author") 27 author = json.getString("author")
28 vendor = json.getString("vendor") 28 vendor = json.getString("vendor")
29 driverVersion = json.getString("driverVersion") 29 version = json.getString("driverVersion")
30 minApi = json.getInt("minApi") 30 minApi = json.getInt("minApi")
31 libraryName = json.getString("libraryName") 31 libraryName = json.getString("libraryName")
32 } catch (e: JSONException) { 32 } catch (e: JSONException) {
@@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
36 } 36 }
37 } 37 }
38 38
39 companion object { 39 /**
40 @Throws(IOException::class) 40 * Tries to get driver metadata information from an input stream that's intended to be
41 private fun getStringFromFile(filePath: String): String { 41 * from a zip file
42 val path = Paths.get(filePath) 42 *
43 val bytes = Files.readAllBytes(path) 43 * @param metadataStream ZipEntry input stream
44 return String(bytes, StandardCharsets.UTF_8) 44 * @param size Size of the file in bytes
45 */
46 constructor(metadataStream: InputStream, size: Long) {
47 if (size > MAX_META_SIZE_BYTES) {
48 return
45 } 49 }
50
51 try {
52 val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
53 name = json.getString("name")
54 description = json.getString("description")
55 author = json.getString("author")
56 vendor = json.getString("vendor")
57 version = json.getString("driverVersion")
58 minApi = json.getInt("minApi")
59 libraryName = json.getString("libraryName")
60 } catch (e: JSONException) {
61 // JSON is malformed, ignore and treat as unsupported metadata.
62 } catch (e: IOException) {
63 // File is inaccessible, ignore and treat as unsupported metadata.
64 }
65 }
66
67 /**
68 * Creates an empty metadata instance
69 */
70 constructor()
71
72 override fun equals(other: Any?): Boolean {
73 if (other !is GpuDriverMetadata) {
74 return false
75 }
76
77 return other.name == name &&
78 other.description == description &&
79 other.author == author &&
80 other.vendor == vendor &&
81 other.version == version &&
82 other.minApi == minApi &&
83 other.libraryName == libraryName
84 }
85
86 override fun hashCode(): Int {
87 var result = name?.hashCode() ?: 0
88 result = 31 * result + (description?.hashCode() ?: 0)
89 result = 31 * result + (author?.hashCode() ?: 0)
90 result = 31 * result + (vendor?.hashCode() ?: 0)
91 result = 31 * result + (version?.hashCode() ?: 0)
92 result = 31 * result + minApi
93 result = 31 * result + (libraryName?.hashCode() ?: 0)
94 return result
95 }
96
97 override fun toString(): String =
98 """
99 Name - $name
100 Description - $description
101 Author - $author
102 Vendor - $vendor
103 Version - $version
104 Min API - $minApi
105 Library Name - $libraryName
106 """.trimMargin().trimIndent()
107
108 var name: String? = null
109 var description: String? = null
110 var author: String? = null
111 var vendor: String? = null
112 var version: String? = null
113 var minApi = 0
114 var libraryName: String? = null
115
116 companion object {
117 private const val MAX_META_SIZE_BYTES = 500000
46 } 118 }
47} 119}
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_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_build.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="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..d26a79711
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_delete.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="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
9</vector>
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_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
new file mode 100644
index 000000000..1dd9a6d7d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -0,0 +1,89 @@
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 android:background="?attr/selectableItemBackground"
11 android:clickable="true"
12 android:focusable="true">
13
14 <LinearLayout
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:orientation="horizontal"
18 android:layout_gravity="center"
19 android:padding="16dp">
20
21 <RadioButton
22 android:id="@+id/radio_button"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_gravity="center_vertical"
26 android:clickable="false"
27 android:checked="false" />
28
29 <LinearLayout
30 android:layout_width="0dp"
31 android:layout_height="wrap_content"
32 android:layout_weight="1"
33 android:orientation="vertical"
34 android:layout_gravity="center_vertical">
35
36 <com.google.android.material.textview.MaterialTextView
37 android:id="@+id/title"
38 style="@style/TextAppearance.Material3.TitleMedium"
39 android:layout_width="match_parent"
40 android:layout_height="wrap_content"
41 android:ellipsize="none"
42 android:marqueeRepeatLimit="marquee_forever"
43 android:requiresFadingEdge="horizontal"
44 android:singleLine="true"
45 android:textAlignment="viewStart"
46 tools:text="@string/select_gpu_driver_default" />
47
48 <com.google.android.material.textview.MaterialTextView
49 android:id="@+id/version"
50 style="@style/TextAppearance.Material3.BodyMedium"
51 android:layout_width="match_parent"
52 android:layout_height="wrap_content"
53 android:layout_marginTop="6dp"
54 android:ellipsize="none"
55 android:marqueeRepeatLimit="marquee_forever"
56 android:requiresFadingEdge="horizontal"
57 android:singleLine="true"
58 android:textAlignment="viewStart"
59 tools:text="@string/install_gpu_driver_description" />
60
61 <com.google.android.material.textview.MaterialTextView
62 android:id="@+id/description"
63 style="@style/TextAppearance.Material3.BodyMedium"
64 android:layout_width="match_parent"
65 android:layout_height="wrap_content"
66 android:layout_marginTop="6dp"
67 android:ellipsize="none"
68 android:marqueeRepeatLimit="marquee_forever"
69 android:requiresFadingEdge="horizontal"
70 android:singleLine="true"
71 android:textAlignment="viewStart"
72 tools:text="@string/install_gpu_driver_description" />
73
74 </LinearLayout>
75
76 <Button
77 android:id="@+id/button_delete"
78 style="@style/Widget.Material3.Button.IconButton"
79 android:layout_width="wrap_content"
80 android:layout_height="wrap_content"
81 android:layout_gravity="center_vertical"
82 android:contentDescription="@string/delete"
83 android:tooltipText="@string/delete"
84 app:icon="@drawable/ic_delete"
85 app:iconTint="?attr/colorControlNormal" />
86
87 </LinearLayout>
88
89</com.google.android.material.card.MaterialCardView>
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_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
new file mode 100644
index 000000000..6cea2d164
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 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 <androidx.coordinatorlayout.widget.CoordinatorLayout
10 android:layout_width="match_parent"
11 android:layout_height="match_parent">
12
13 <com.google.android.material.appbar.AppBarLayout
14 android:id="@+id/appbar_drivers"
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:fitsSystemWindows="true"
18 app:liftOnScrollTargetViewId="@id/list_drivers">
19
20 <com.google.android.material.appbar.MaterialToolbar
21 android:id="@+id/toolbar_drivers"
22 android:layout_width="match_parent"
23 android:layout_height="?attr/actionBarSize"
24 app:navigationIcon="@drawable/ic_back"
25 app:title="@string/gpu_driver_manager" />
26
27 </com.google.android.material.appbar.AppBarLayout>
28
29 <androidx.recyclerview.widget.RecyclerView
30 android:id="@+id/list_drivers"
31 android:layout_width="match_parent"
32 android:layout_height="match_parent"
33 android:clipToPadding="false"
34 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
35
36 </androidx.coordinatorlayout.widget.CoordinatorLayout>
37
38 <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
39 android:id="@+id/button_install"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_gravity="bottom|end"
43 android:text="@string/install"
44 app:icon="@drawable/ic_add"
45 app:layout_constraintBottom_toBottomOf="parent"
46 app:layout_constraintEnd_toEndOf="parent" />
47
48</androidx.constraintlayout.widget.ConstraintLayout>
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..82749359d 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,12 @@
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" />
25 <action
26 android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
27 app:destination="@id/driverManagerFragment" />
22 </fragment> 28 </fragment>
23 29
24 <fragment 30 <fragment
@@ -88,5 +94,13 @@
88 <action 94 <action
89 android:id="@+id/action_global_settingsActivity" 95 android:id="@+id/action_global_settingsActivity"
90 app:destination="@id/settingsActivity" /> 96 app:destination="@id/settingsActivity" />
97 <fragment
98 android:id="@+id/installableFragment"
99 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
100 android:label="InstallableFragment" />
101 <fragment
102 android:id="@+id/driverManagerFragment"
103 android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
104 android:label="DriverManagerFragment" />
91 105
92</navigation> 106</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..72a47fbdb 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>
@@ -169,9 +168,7 @@
169 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> 168 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
170 <string name="select_gpu_driver_install">Installieren</string> 169 <string name="select_gpu_driver_install">Installieren</string>
171 <string name="select_gpu_driver_default">Standard</string> 170 <string name="select_gpu_driver_default">Standard</string>
172 <string name="select_gpu_driver_install_success">%s wurde installiert</string>
173 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string> 171 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
174 <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
175 <string name="system_gpu_driver">System GPU-Treiber</string> 172 <string name="system_gpu_driver">System GPU-Treiber</string>
176 <string name="installing_driver">Treiber wird installiert...</string> 173 <string name="installing_driver">Treiber wird installiert...</string>
177 174
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..e5bdd5889 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> 171 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
173 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Predeterminado</string> 173 <string name="select_gpu_driver_default">Predeterminado</string>
175 <string name="select_gpu_driver_install_success">Instalado %s</string>
176 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string> 174 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
177 <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
178 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
179 <string name="installing_driver">Instalando driver...</string> 176 <string name="installing_driver">Instalando driver...</string>
180 177
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..1e02828aa 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> 171 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
173 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
174 <string name="select_gpu_driver_default">Défaut</string> 173 <string name="select_gpu_driver_default">Défaut</string>
175 <string name="select_gpu_driver_install_success">%s Installé</string>
176 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string> 174 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
177 <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
178 <string name="system_gpu_driver">Pilote du GPU du système</string> 175 <string name="system_gpu_driver">Pilote du GPU du système</string>
179 <string name="installing_driver">Installation du pilote...</string> 176 <string name="installing_driver">Installation du pilote...</string>
180 177
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..09c9345b0 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> 171 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
173 <string name="select_gpu_driver_install">Installa</string> 172 <string name="select_gpu_driver_install">Installa</string>
174 <string name="select_gpu_driver_default">Predefinito</string> 173 <string name="select_gpu_driver_default">Predefinito</string>
175 <string name="select_gpu_driver_install_success">Installato%s</string>
176 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string> 174 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
177 <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
178 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
179 <string name="installing_driver">Installando i driver...</string> 176 <string name="installing_driver">Installando i driver...</string>
180 177
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..a0ea78bef 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>
@@ -171,9 +170,7 @@
171 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string> 170 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
172 <string name="select_gpu_driver_install">インストール</string> 171 <string name="select_gpu_driver_install">インストール</string>
173 <string name="select_gpu_driver_default">デフォルト</string> 172 <string name="select_gpu_driver_default">デフォルト</string>
174 <string name="select_gpu_driver_install_success">%s をインストールしました</string>
175 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string> 173 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
176 <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
177 <string name="system_gpu_driver">システムのGPUドライバ</string> 174 <string name="system_gpu_driver">システムのGPUドライバ</string>
178 <string name="installing_driver">インストール中…</string> 175 <string name="installing_driver">インストール中…</string>
179 176
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..214f95706 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> 171 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
173 <string name="select_gpu_driver_install">설치</string> 172 <string name="select_gpu_driver_install">설치</string>
174 <string name="select_gpu_driver_default">기본값</string> 173 <string name="select_gpu_driver_default">기본값</string>
175 <string name="select_gpu_driver_install_success">설치된 %s</string>
176 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string> 174 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
177 <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
178 <string name="system_gpu_driver">시스템 GPU 드라이버</string> 175 <string name="system_gpu_driver">시스템 GPU 드라이버</string>
179 <string name="installing_driver">드라이버 설치 중...</string> 176 <string name="installing_driver">드라이버 설치 중...</string>
180 177
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..5443cef42 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> 171 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
173 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
174 <string name="select_gpu_driver_default">Standard</string> 173 <string name="select_gpu_driver_default">Standard</string>
175 <string name="select_gpu_driver_install_success">Installert %s</string>
176 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string> 174 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
177 <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
178 <string name="system_gpu_driver">Systemets GPU-driver</string> 175 <string name="system_gpu_driver">Systemets GPU-driver</string>
179 <string name="installing_driver">Installerer driver...</string> 176 <string name="installing_driver">Installerer driver...</string>
180 177
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..899e233d0 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> 171 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
173 <string name="select_gpu_driver_install">Zainstaluj</string> 172 <string name="select_gpu_driver_install">Zainstaluj</string>
174 <string name="select_gpu_driver_default">Domyślne</string> 173 <string name="select_gpu_driver_default">Domyślne</string>
175 <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
176 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string> 174 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
177 <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
178 <string name="system_gpu_driver">Systemowy sterownik GPU</string> 175 <string name="system_gpu_driver">Systemowy sterownik GPU</string>
179 <string name="installing_driver">Instalowanie sterownika...</string> 176 <string name="installing_driver">Instalowanie sterownika...</string>
180 177
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..caa095364 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
173 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
175 <string name="select_gpu_driver_install_success">Instalado%s</string>
176 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
177 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
178 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
179 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
180 177
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..0a1a47fbb 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
173 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
175 <string name="select_gpu_driver_install_success">Instalado%s</string>
176 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
177 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
178 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
179 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
180 177
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..0bef035d6 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
173 <string name="select_gpu_driver_install">Установить</string> 172 <string name="select_gpu_driver_install">Установить</string>
174 <string name="select_gpu_driver_default">По умолчанию</string> 173 <string name="select_gpu_driver_default">По умолчанию</string>
175 <string name="select_gpu_driver_install_success">Установлено %s</string>
176 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string> 174 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
177 <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
178 <string name="system_gpu_driver">Системный драйвер ГП</string> 175 <string name="system_gpu_driver">Системный драйвер ГП</string>
179 <string name="installing_driver">Установка драйвера...</string> 176 <string name="installing_driver">Установка драйвера...</string>
180 177
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..5b789ee98 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
173 <string name="select_gpu_driver_install">Встановити</string> 172 <string name="select_gpu_driver_install">Встановити</string>
174 <string name="select_gpu_driver_default">За замовчуванням</string> 173 <string name="select_gpu_driver_default">За замовчуванням</string>
175 <string name="select_gpu_driver_install_success">Встановлено %s</string>
176 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string> 174 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
177 <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
178 <string name="system_gpu_driver">Системний драйвер ГП</string> 175 <string name="system_gpu_driver">Системний драйвер ГП</string>
179 <string name="installing_driver">Встановлення драйвера...</string> 176 <string name="installing_driver">Встановлення драйвера...</string>
180 177
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..c0e885751 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> 171 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
173 <string name="select_gpu_driver_install">安装</string> 172 <string name="select_gpu_driver_install">安装</string>
174 <string name="select_gpu_driver_default">系统默认</string> 173 <string name="select_gpu_driver_default">系统默认</string>
175 <string name="select_gpu_driver_install_success">已安装 %s</string>
176 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string> 174 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
177 <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
178 <string name="system_gpu_driver">系统 GPU 驱动程序</string> 175 <string name="system_gpu_driver">系统 GPU 驱动程序</string>
179 <string name="installing_driver">正在安装驱动程序…</string> 176 <string name="installing_driver">正在安装驱动程序…</string>
180 177
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..4a21bf893 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>
@@ -172,9 +171,7 @@
172 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> 171 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
173 <string name="select_gpu_driver_install">安裝</string> 172 <string name="select_gpu_driver_install">安裝</string>
174 <string name="select_gpu_driver_default">預設</string> 173 <string name="select_gpu_driver_default">預設</string>
175 <string name="select_gpu_driver_install_success">已安裝 %s</string>
176 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string> 174 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
177 <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
178 <string name="system_gpu_driver">系統 GPU 驅動程式</string> 175 <string name="system_gpu_driver">系統 GPU 驅動程式</string>
179 <string name="installing_driver">正在安裝驅動程式…</string> 176 <string name="installing_driver">正在安裝驅動程式…</string>
180 177
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 7b2296d95..ef855ea6f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,6 +13,8 @@
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen> 15 <dimen name="icon_inset">24dp</dimen>
16 <dimen name="spacing_bottom_list_fab">72dp</dimen>
17 <dimen name="spacing_fab">24dp</dimen>
16 18
17 <dimen name="dialog_margin">20dp</dimen> 19 <dimen name="dialog_margin">20dp</dimen>
18 <dimen name="elevated_app_bar">3dp</dimen> 20 <dimen name="elevated_app_bar">3dp</dimen>
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..9e4854221 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -72,6 +72,7 @@
72 <string name="invalid_keys_error">Invalid encryption keys</string> 72 <string name="invalid_keys_error">Invalid encryption keys</string>
73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> 73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> 74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
75 <string name="gpu_driver_manager">GPU Driver Manager</string>
75 <string name="install_gpu_driver">Install GPU driver</string> 76 <string name="install_gpu_driver">Install GPU driver</string>
76 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> 77 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
77 <string name="advanced_settings">Advanced settings</string> 78 <string name="advanced_settings">Advanced settings</string>
@@ -90,7 +91,6 @@
90 <string name="manage_save_data">Manage save data</string> 91 <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> 92 <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> 93 <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> 94 <string name="save_file_imported_success">Imported successfully</string>
95 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> 95 <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> 96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -101,12 +101,13 @@
101 <string name="firmware_installing">Installing firmware</string> 101 <string name="firmware_installing">Installing firmware</string>
102 <string name="firmware_installed_success">Firmware installed successfully</string> 102 <string name="firmware_installed_success">Firmware installed successfully</string>
103 <string name="firmware_installed_failure">Firmware installation failed</string> 103 <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> 104 <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> 105 <string name="share_log">Share debug logs</string>
106 <string name="share_log_description">Share yuzu\'s log file to debug issues</string> 106 <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> 107 <string name="share_log_missing">No log file found</string>
108 <string name="install_game_content">Install game content</string> 108 <string name="install_game_content">Install game content</string>
109 <string name="install_game_content_description">Install game updates or DLC</string> 109 <string name="install_game_content_description">Install game updates or DLC</string>
110 <string name="installing_game_content">Installing content…</string>
110 <string name="install_game_content_failure">Error installing file(s) to NAND</string> 111 <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> 112 <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> 113 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
@@ -118,6 +119,10 @@
118 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> 119 <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> 120 <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> 121 <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>
122 <string name="manage_yuzu_data">Manage yuzu data</string>
123 <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
124 <string name="share_save_file">Share save file</string>
125 <string name="export_save_failed">Failed to export save</string>
121 126
122 <!-- About screen strings --> 127 <!-- About screen strings -->
123 <string name="gaia_is_not_real">Gaia isn\'t real</string> 128 <string name="gaia_is_not_real">Gaia isn\'t real</string>
@@ -128,6 +133,16 @@
128 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string> 133 <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> 134 <string name="licenses_description">Projects that make yuzu for Android possible</string>
130 <string name="build">Build</string> 135 <string name="build">Build</string>
136 <string name="user_data">User data</string>
137 <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
138 <string name="exporting_user_data">Exporting user data…</string>
139 <string name="importing_user_data">Importing user data…</string>
140 <string name="import_user_data">Import user data</string>
141 <string name="invalid_yuzu_backup">Invalid yuzu backup</string>
142 <string name="user_data_export_success">User data exported successfully</string>
143 <string name="user_data_import_success">User data imported successfully</string>
144 <string name="user_data_export_cancelled">Export cancelled</string>
145 <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> 146 <string name="support_link">https://discord.gg/u77vRWY</string>
132 <string name="website_link">https://yuzu-emu.org/</string> 147 <string name="website_link">https://yuzu-emu.org/</string>
133 <string name="github_link">https://github.com/yuzu-emu</string> 148 <string name="github_link">https://github.com/yuzu-emu</string>
@@ -215,15 +230,22 @@
215 <string name="auto">Auto</string> 230 <string name="auto">Auto</string>
216 <string name="submit">Submit</string> 231 <string name="submit">Submit</string>
217 <string name="string_null">Null</string> 232 <string name="string_null">Null</string>
233 <string name="string_import">Import</string>
234 <string name="export">Export</string>
235 <string name="export_failed">Export failed</string>
236 <string name="import_failed">Import failed</string>
237 <string name="cancelling">Cancelling</string>
238 <string name="install">Install</string>
239 <string name="delete">Delete</string>
218 240
219 <!-- GPU driver installation --> 241 <!-- GPU driver installation -->
220 <string name="select_gpu_driver">Select GPU driver</string> 242 <string name="select_gpu_driver">Select GPU driver</string>
221 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> 243 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
222 <string name="select_gpu_driver_install">Install</string> 244 <string name="select_gpu_driver_install">Install</string>
223 <string name="select_gpu_driver_default">Default</string> 245 <string name="select_gpu_driver_default">Default</string>
224 <string name="select_gpu_driver_install_success">Installed %s</string>
225 <string name="select_gpu_driver_use_default">Using default GPU driver</string> 246 <string name="select_gpu_driver_use_default">Using default GPU driver</string>
226 <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> 247 <string name="select_gpu_driver_error">Invalid driver selected</string>
248 <string name="driver_already_installed">Driver already installed</string>
227 <string name="system_gpu_driver">System GPU driver</string> 249 <string name="system_gpu_driver">System GPU driver</string>
228 <string name="installing_driver">Installing driver…</string> 250 <string name="installing_driver">Installing driver…</string>
229 251
@@ -281,6 +303,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> 303 <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> 304 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
283 <string name="memory_formatted">%1$s %2$s</string> 305 <string name="memory_formatted">%1$s %2$s</string>
306 <string name="no_game_present">No bootable game present!</string>
284 307
285 <!-- Region Names --> 308 <!-- Region Names -->
286 <string name="region_japan">Japan</string> 309 <string name="region_japan">Japan</string>
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
3 3
4// Top-level build file where you can add configuration options common to all sub-projects/modules. 4// Top-level build file where you can add configuration options common to all sub-projects/modules.
5plugins { 5plugins {
6 id("com.android.application") version "8.0.2" apply false 6 id("com.android.application") version "8.1.2" apply false
7 id("com.android.library") version "8.0.2" apply false 7 id("com.android.library") version "8.1.2" apply false
8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false 8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false
9} 9}
10 10
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 972d5e45b..ef301d8b4 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -77,6 +77,7 @@ void AudioRenderer::Wait() {
77 "{}, got {}", 77 "{}, got {}",
78 Message::RenderResponse, msg); 78 Message::RenderResponse, msg);
79 } 79 }
80 PostDSPClearCommandBuffer();
80} 81}
81 82
82void AudioRenderer::Send(Direction dir, u32 message) { 83void AudioRenderer::Send(Direction dir, u32 message) {
@@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
96 command_buffers[session_id].reset_buffer = reset; 97 command_buffers[session_id].reset_buffer = reset;
97} 98}
98 99
100void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
101 for (auto& buffer : command_buffers) {
102 buffer.buffer = 0;
103 buffer.size = 0;
104 buffer.reset_buffer = false;
105 }
106}
107
99u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { 108u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
100 return command_buffers[session_id].remaining_command_count; 109 return command_buffers[session_id].remaining_command_count;
101} 110}
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 85874d88a..57b89d9fe 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -85,6 +85,8 @@ private:
85 */ 85 */
86 void CreateSinkStreams(); 86 void CreateSinkStreams();
87 87
88 void PostDSPClearCommandBuffer() noexcept;
89
88 /// Core system 90 /// Core system
89 Core::System& system; 91 Core::System& system;
90 /// The output sink the AudioRenderer will send samples to 92 /// The output sink the AudioRenderer will send samples to
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/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 6081352a2..d66d04fae 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
204 // paused and we'll desync, so just play silence. 204 // paused and we'll desync, so just play silence.
205 if (system.IsPaused() || system.IsShuttingDown()) { 205 if (system.IsPaused() || system.IsShuttingDown()) {
206 if (system.IsShuttingDown()) { 206 if (system.IsShuttingDown()) {
207 {
208 std::scoped_lock lk{release_mutex};
209 queued_buffers.store(0);
210 }
207 release_cv.notify_one(); 211 release_cv.notify_one();
208 } 212 }
209 213
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/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
39#define Crash() exit(1) 39#define Crash() exit(1)
40#endif 40#endif
41 41
42#define LTO_NOINLINE __attribute__((noinline))
43
42#else // _MSC_VER 44#else // _MSC_VER
43 45
46#define LTO_NOINLINE
47
44// Locale Cross-Compatibility 48// Locale Cross-Compatibility
45#define locale_t _locale_t 49#define locale_t _locale_t
46 50
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
211 Elf64_Sxword r_addend; /* Addend */ 211 Elf64_Sxword r_addend; /* Addend */
212}; 212};
213 213
214/* RELR relocation table entry */
215
216using Elf32_Relr = Elf32_Word;
217using Elf64_Relr = Elf64_Xword;
218
214/* How to extract and insert information held in the r_info field. */ 219/* How to extract and insert information held in the r_info field. */
215 220
216static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { 221static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
328constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ 333constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
329constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ 334constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
330constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ 335constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
336constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
337constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
338constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
331 339
332} // namespace ELF 340} // namespace ELF
333} // namespace Common 341} // namespace Common
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/polyfill_thread.h b/src/common/polyfill_thread.h
index 41cbb9ed5..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
15#include <condition_variable> 15#include <condition_variable>
16#include <stop_token> 16#include <stop_token>
17#include <thread> 17#include <thread>
18#include <utility>
18 19
19namespace Common { 20namespace Common {
20 21
21template <typename Condvar, typename Lock, typename Pred> 22template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { 23void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lk, token, std::move(pred)); 24 cv.wait(lk, token, std::forward<Pred>(pred));
24} 25}
25 26
26template <typename Rep, typename Period> 27template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
109 110
110 // Insert the callback. 111 // Insert the callback.
111 stop_state_callback ret = ++m_next_callback; 112 stop_state_callback ret = ++m_next_callback;
112 m_callbacks.emplace(ret, move(f)); 113 m_callbacks.emplace(ret, std::move(f));
113 return ret; 114 return ret;
114 } 115 }
115 116
@@ -162,7 +163,7 @@ private:
162 friend class stop_source; 163 friend class stop_source;
163 template <typename Callback> 164 template <typename Callback>
164 friend class stop_callback; 165 friend class stop_callback;
165 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {} 166 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
166 167
167private: 168private:
168 shared_ptr<polyfill::stop_state> m_stop_state; 169 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
198private: 199private:
199 friend class jthread; 200 friend class jthread;
200 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) 201 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
201 : m_stop_state(move(stop_state)) {} 202 : m_stop_state(std::move(stop_state)) {}
202 203
203private: 204private:
204 shared_ptr<polyfill::stop_state> m_stop_state; 205 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
218 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 219 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
219 : m_stop_state(st.m_stop_state) { 220 : m_stop_state(st.m_stop_state) {
220 if (m_stop_state) { 221 if (m_stop_state) {
221 m_callback = m_stop_state->insert_callback(move(cb)); 222 m_callback = m_stop_state->insert_callback(std::move(cb));
222 } 223 }
223 } 224 }
224 template <typename C> 225 template <typename C>
225 requires constructible_from<Callback, C> 226 requires constructible_from<Callback, C>
226 explicit stop_callback(stop_token&& st, 227 explicit stop_callback(stop_token&& st,
227 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 228 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
228 : m_stop_state(move(st.m_stop_state)) { 229 : m_stop_state(std::move(st.m_stop_state)) {
229 if (m_stop_state) { 230 if (m_stop_state) {
230 m_callback = m_stop_state->insert_callback(move(cb)); 231 m_callback = m_stop_state->insert_callback(std::move(cb));
231 } 232 }
232 } 233 }
233 ~stop_callback() { 234 ~stop_callback() {
@@ -260,7 +261,7 @@ public:
260 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> 261 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
261 explicit jthread(F&& f, Args&&... args) 262 explicit jthread(F&& f, Args&&... args)
262 : m_stop_state(make_shared<polyfill::stop_state>()), 263 : m_stop_state(make_shared<polyfill::stop_state>()),
263 m_thread(make_thread(move(f), move(args)...)) {} 264 m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
264 265
265 ~jthread() { 266 ~jthread() {
266 if (joinable()) { 267 if (joinable()) {
@@ -317,9 +318,9 @@ private:
317 template <typename F, typename... Args> 318 template <typename F, typename... Args>
318 thread make_thread(F&& f, Args&&... args) { 319 thread make_thread(F&& f, Args&&... args) {
319 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { 320 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
320 return thread(move(f), get_stop_token(), move(args)...); 321 return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
321 } else { 322 } else {
322 return thread(move(f), move(args)...); 323 return thread(std::forward<F>(f), std::forward<Args>(args)...);
323 } 324 }
324 } 325 }
325 326
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 4ecaf550b..98b43e49c 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
45SWITCHABLE(FullscreenMode, true); 45SWITCHABLE(FullscreenMode, true);
46SWITCHABLE(GpuAccuracy, true); 46SWITCHABLE(GpuAccuracy, true);
47SWITCHABLE(Language, true); 47SWITCHABLE(Language, true);
48SWITCHABLE(MemoryLayout, true);
48SWITCHABLE(NvdecEmulation, false); 49SWITCHABLE(NvdecEmulation, false);
49SWITCHABLE(Region, true); 50SWITCHABLE(Region, true);
50SWITCHABLE(RendererBackend, true); 51SWITCHABLE(RendererBackend, true);
@@ -61,6 +62,10 @@ SWITCHABLE(u32, false);
61SWITCHABLE(u8, false); 62SWITCHABLE(u8, false);
62SWITCHABLE(u8, true); 63SWITCHABLE(u8, true);
63 64
65// Used in UISettings
66// TODO see if we can move this to uisettings.cpp
67SWITCHABLE(ConfirmStop, true);
68
64#undef SETTING 69#undef SETTING
65#undef SWITCHABLE 70#undef SWITCHABLE
66#endif 71#endif
@@ -130,13 +135,17 @@ void LogSettings() {
130 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); 135 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
131} 136}
132 137
138void UpdateGPUAccuracy() {
139 values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
140}
141
133bool IsGPULevelExtreme() { 142bool IsGPULevelExtreme() {
134 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme; 143 return values.current_gpu_accuracy == GpuAccuracy::Extreme;
135} 144}
136 145
137bool IsGPULevelHigh() { 146bool IsGPULevelHigh() {
138 return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme || 147 return values.current_gpu_accuracy == GpuAccuracy::Extreme ||
139 values.gpu_accuracy.GetValue() == GpuAccuracy::High; 148 values.current_gpu_accuracy == GpuAccuracy::High;
140} 149}
141 150
142bool IsFastmemEnabled() { 151bool IsFastmemEnabled() {
diff --git a/src/common/settings.h b/src/common/settings.h
index 82ec9077e..236e33bee 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
67SWITCHABLE(FullscreenMode, true); 67SWITCHABLE(FullscreenMode, true);
68SWITCHABLE(GpuAccuracy, true); 68SWITCHABLE(GpuAccuracy, true);
69SWITCHABLE(Language, true); 69SWITCHABLE(Language, true);
70SWITCHABLE(MemoryLayout, true);
70SWITCHABLE(NvdecEmulation, false); 71SWITCHABLE(NvdecEmulation, false);
71SWITCHABLE(Region, true); 72SWITCHABLE(Region, true);
72SWITCHABLE(RendererBackend, true); 73SWITCHABLE(RendererBackend, true);
@@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
83SWITCHABLE(u8, false); 84SWITCHABLE(u8, false);
84SWITCHABLE(u8, true); 85SWITCHABLE(u8, true);
85 86
87// Used in UISettings
88// TODO see if we can move this to uisettings.h
89SWITCHABLE(ConfirmStop, true);
90
86#undef SETTING 91#undef SETTING
87#undef SWITCHABLE 92#undef SWITCHABLE
88#endif 93#endif
@@ -307,6 +312,7 @@ struct Values {
307 Specialization::Default, 312 Specialization::Default,
308 true, 313 true,
309 true}; 314 true};
315 GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
310 SwitchableSetting<AnisotropyMode, true> max_anisotropy{ 316 SwitchableSetting<AnisotropyMode, true> max_anisotropy{
311 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, 317 linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
312 "max_anisotropy", Category::RendererAdvanced}; 318 "max_anisotropy", Category::RendererAdvanced};
@@ -350,6 +356,8 @@ struct Values {
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 356 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", 357 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug}; 358 Category::RendererDebug};
359 // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
360 bool renderer_amdvlk_depth_bias_workaround{};
353 361
354 // System 362 // System
355 SwitchableSetting<Language, true> language_index{linkage, 363 SwitchableSetting<Language, true> language_index{linkage,
@@ -522,6 +530,7 @@ struct Values {
522 530
523extern Values values; 531extern Values values;
524 532
533void UpdateGPUAccuracy();
525bool IsGPULevelExtreme(); 534bool IsGPULevelExtreme();
526bool IsGPULevelHigh(); 535bool IsGPULevelHigh();
527 536
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 815cafe15..11429d7a8 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
133 133
134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); 134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
135 135
136ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
137
136ENUM(FullscreenMode, Borderless, Exclusive); 138ENUM(FullscreenMode, Borderless, Exclusive);
137 139
138ENUM(NvdecEmulation, Off, Cpu, Gpu); 140ENUM(NvdecEmulation, Off, Cpu, Gpu);
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..0b0cef984 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,
@@ -3384,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
3384 new_attr, KMemoryBlockDisableMergeAttribute::Locked, 3405 new_attr, KMemoryBlockDisableMergeAttribute::Locked,
3385 KMemoryBlockDisableMergeAttribute::None); 3406 KMemoryBlockDisableMergeAttribute::None);
3386 3407
3408 // If we have an output page group, open.
3409 if (out_pg) {
3410 out_pg->Open();
3411 }
3412
3387 R_SUCCEED(); 3413 R_SUCCEED();
3388} 3414}
3389 3415
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/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
373 static inline thread_local u8 host_thread_id = UINT8_MAX; 373 static inline thread_local u8 host_thread_id = UINT8_MAX;
374 374
375 /// Sets the host thread ID for the caller. 375 /// Sets the host thread ID for the caller.
376 u32 SetHostThreadId(std::size_t core_id) { 376 LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
377 // This should only be called during core init. 377 // This should only be called during core init.
378 ASSERT(host_thread_id == UINT8_MAX); 378 ASSERT(host_thread_id == UINT8_MAX);
379 379
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
384 } 384 }
385 385
386 /// Gets the host thread ID for the caller 386 /// Gets the host thread ID for the caller
387 u32 GetHostThreadId() const { 387 LTO_NOINLINE u32 GetHostThreadId() const {
388 return host_thread_id; 388 return host_thread_id;
389 } 389 }
390 390
391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
392 KThread* GetHostDummyThread(KThread* existing_thread) { 392 LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
393 const auto initialize{[](KThread* thread) { 393 const auto initialize{[](KThread* thread) LTO_NOINLINE {
394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); 394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
395 return thread; 395 return thread;
396 }}; 396 }};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
424 424
425 static inline thread_local bool is_phantom_mode_for_singlecore{false}; 425 static inline thread_local bool is_phantom_mode_for_singlecore{false};
426 426
427 bool IsPhantomModeForSingleCore() const { 427 LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
428 return is_phantom_mode_for_singlecore; 428 return is_phantom_mode_for_singlecore;
429 } 429 }
430 430
431 void SetIsPhantomModeForSingleCore(bool value) { 431 LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
432 ASSERT(!is_multicore); 432 ASSERT(!is_multicore);
433 is_phantom_mode_for_singlecore = value; 433 is_phantom_mode_for_singlecore = value;
434 } 434 }
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
439 439
440 static inline thread_local KThread* current_thread{nullptr}; 440 static inline thread_local KThread* current_thread{nullptr};
441 441
442 KThread* GetCurrentEmuThread() { 442 LTO_NOINLINE KThread* GetCurrentEmuThread() {
443 if (!current_thread) { 443 if (!current_thread) {
444 current_thread = GetHostDummyThread(nullptr); 444 current_thread = GetHostDummyThread(nullptr);
445 } 445 }
446 return current_thread; 446 return current_thread;
447 } 447 }
448 448
449 void SetCurrentEmuThread(KThread* thread) { 449 LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
450 current_thread = thread; 450 current_thread = thread;
451 } 451 }
452 452
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/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index b971401e6..b7d14060c 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -49,7 +49,7 @@ public:
49 : ServiceFramework{system_, "IManagerForSystemService"} { 49 : ServiceFramework{system_, "IManagerForSystemService"} {
50 // clang-format off 50 // clang-format off
51 static const FunctionInfo functions[] = { 51 static const FunctionInfo functions[] = {
52 {0, nullptr, "CheckAvailability"}, 52 {0, &IManagerForSystemService::CheckAvailability, "CheckAvailability"},
53 {1, nullptr, "GetAccountId"}, 53 {1, nullptr, "GetAccountId"},
54 {2, nullptr, "EnsureIdTokenCacheAsync"}, 54 {2, nullptr, "EnsureIdTokenCacheAsync"},
55 {3, nullptr, "LoadIdTokenCache"}, 55 {3, nullptr, "LoadIdTokenCache"},
@@ -78,6 +78,13 @@ public:
78 78
79 RegisterHandlers(functions); 79 RegisterHandlers(functions);
80 } 80 }
81
82private:
83 void CheckAvailability(HLERequestContext& ctx) {
84 LOG_WARNING(Service_ACC, "(STUBBED) called");
85 IPC::ResponseBuilder rb{ctx, 2};
86 rb.Push(ResultSuccess);
87 }
81}; 88};
82 89
83// 3.0.0+ 90// 3.0.0+
@@ -837,6 +844,29 @@ void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) {
837 rb.Push(ResultSuccess); 844 rb.Push(ResultSuccess);
838} 845}
839 846
847void Module::Interface::BeginUserRegistration(HLERequestContext& ctx) {
848 const auto user_id = Common::UUID::MakeRandom();
849 profile_manager->CreateNewUser(user_id, "yuzu");
850
851 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
852
853 IPC::ResponseBuilder rb{ctx, 6};
854 rb.Push(ResultSuccess);
855 rb.PushRaw(user_id);
856}
857
858void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
859 IPC::RequestParser rp{ctx};
860 Common::UUID user_id = rp.PopRaw<Common::UUID>();
861
862 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
863
864 profile_manager->WriteUserSaveFile();
865
866 IPC::ResponseBuilder rb{ctx, 2};
867 rb.Push(ResultSuccess);
868}
869
840void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { 870void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
841 IPC::RequestParser rp{ctx}; 871 IPC::RequestParser rp{ctx};
842 Common::UUID user_id = rp.PopRaw<Common::UUID>(); 872 Common::UUID user_id = rp.PopRaw<Common::UUID>();
@@ -880,6 +910,17 @@ void Module::Interface::StoreSaveDataThumbnailApplication(HLERequestContext& ctx
880 StoreSaveDataThumbnail(ctx, uuid, tid); 910 StoreSaveDataThumbnail(ctx, uuid, tid);
881} 911}
882 912
913void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& ctx) {
914 IPC::RequestParser rp{ctx};
915 const auto uuid = rp.PopRaw<Common::UUID>();
916
917 LOG_INFO(Service_ACC, "called, uuid=0x{}", uuid.RawString());
918
919 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
920 rb.Push(ResultSuccess);
921 rb.PushIpcInterface<IManagerForSystemService>(system, uuid);
922}
923
883void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { 924void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
884 IPC::RequestParser rp{ctx}; 925 IPC::RequestParser rp{ctx};
885 const auto uuid = rp.PopRaw<Common::UUID>(); 926 const auto uuid = rp.PopRaw<Common::UUID>();
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 6b4735c2f..0395229b4 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -33,10 +33,13 @@ public:
33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx); 33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx);
34 void IsUserAccountSwitchLocked(HLERequestContext& ctx); 34 void IsUserAccountSwitchLocked(HLERequestContext& ctx);
35 void InitializeApplicationInfoV2(HLERequestContext& ctx); 35 void InitializeApplicationInfoV2(HLERequestContext& ctx);
36 void BeginUserRegistration(HLERequestContext& ctx);
37 void CompleteUserRegistration(HLERequestContext& ctx);
36 void GetProfileEditor(HLERequestContext& ctx); 38 void GetProfileEditor(HLERequestContext& ctx);
37 void ListQualifiedUsers(HLERequestContext& ctx); 39 void ListQualifiedUsers(HLERequestContext& ctx);
38 void ListOpenContextStoredUsers(HLERequestContext& ctx); 40 void ListOpenContextStoredUsers(HLERequestContext& ctx);
39 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); 41 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx);
42 void GetBaasAccountManagerForSystemService(HLERequestContext& ctx);
40 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); 43 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx);
41 44
42 private: 45 private:
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d9882ecd3..770d13ec5 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -23,7 +23,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
23 {99, nullptr, "DebugActivateOpenContextRetention"}, 23 {99, nullptr, "DebugActivateOpenContextRetention"},
24 {100, nullptr, "GetUserRegistrationNotifier"}, 24 {100, nullptr, "GetUserRegistrationNotifier"},
25 {101, nullptr, "GetUserStateChangeNotifier"}, 25 {101, nullptr, "GetUserStateChangeNotifier"},
26 {102, nullptr, "GetBaasAccountManagerForSystemService"}, 26 {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, 27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
28 {104, nullptr, "GetProfileUpdateNotifier"}, 28 {104, nullptr, "GetProfileUpdateNotifier"},
29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, 29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
@@ -40,8 +40,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, 40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
41 {190, nullptr, "GetUserLastOpenedApplication"}, 41 {190, nullptr, "GetUserLastOpenedApplication"},
42 {191, nullptr, "ActivateOpenContextHolder"}, 42 {191, nullptr, "ActivateOpenContextHolder"},
43 {200, nullptr, "BeginUserRegistration"}, 43 {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
44 {201, nullptr, "CompleteUserRegistration"}, 44 {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
45 {202, nullptr, "CancelUserRegistration"}, 45 {202, nullptr, "CancelUserRegistration"},
46 {203, nullptr, "DeleteUser"}, 46 {203, nullptr, "DeleteUser"},
47 {204, nullptr, "SetUserPosition"}, 47 {204, nullptr, "SetUserPosition"},
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 993a5a57a..900e32200 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -96,9 +96,10 @@ public:
96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, 96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
97 const UserData& data_new); 97 const UserData& data_new);
98 98
99 void WriteUserSaveFile();
100
99private: 101private:
100 void ParseUserSaveFile(); 102 void ParseUserSaveFile();
101 void WriteUserSaveFile();
102 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); 103 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
103 bool RemoveProfileAtIndex(std::size_t index); 104 bool RemoveProfileAtIndex(std::size_t index);
104 105
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8ffdd19e7..98765b81a 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"},
@@ -205,10 +210,10 @@ IDisplayController::IDisplayController(Core::System& system_)
205 {21, nullptr, "ClearAppletTransitionBuffer"}, 210 {21, nullptr, "ClearAppletTransitionBuffer"},
206 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, 211 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
207 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, 212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
208 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, 213 {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"},
209 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, 214 {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "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,54 @@ 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::AcquireLastForegroundCaptureSharedBuffer(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::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
252 LOG_WARNING(Service_AM, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
258void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
259 LOG_WARNING(Service_AM, "(STUBBED) called");
260
261 IPC::ResponseBuilder rb{ctx, 4};
262 rb.Push(ResultSuccess);
263 rb.Push(1U);
264 rb.Push(0);
265}
266
267void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
268 LOG_WARNING(Service_AM, "(STUBBED) called");
269
270 IPC::ResponseBuilder rb{ctx, 2};
271 rb.Push(ResultSuccess);
272}
273
221IDebugFunctions::IDebugFunctions(Core::System& system_) 274IDebugFunctions::IDebugFunctions(Core::System& system_)
222 : ServiceFramework{system_, "IDebugFunctions"} { 275 : ServiceFramework{system_, "IDebugFunctions"} {
223 // clang-format off 276 // clang-format off
@@ -277,14 +330,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger&
277 {20, nullptr, "SetDesirableKeyboardLayout"}, 330 {20, nullptr, "SetDesirableKeyboardLayout"},
278 {21, nullptr, "GetScreenShotProgramId"}, 331 {21, nullptr, "GetScreenShotProgramId"},
279 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, 332 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
280 {41, nullptr, "IsSystemBufferSharingEnabled"}, 333 {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"},
281 {42, nullptr, "GetSystemSharedLayerHandle"}, 334 {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"},
282 {43, nullptr, "GetSystemSharedBufferHandle"}, 335 {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"},
283 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, 336 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
284 {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, 337 {45, nullptr, "SetManagedDisplayLayerSeparationMode"},
285 {46, nullptr, "SetRecordingLayerCompositionEnabled"}, 338 {46, nullptr, "SetRecordingLayerCompositionEnabled"},
286 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, 339 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
287 {51, nullptr, "ApproveToDisplay"}, 340 {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
288 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, 341 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
289 {61, nullptr, "SetMediaPlaybackState"}, 342 {61, nullptr, "SetMediaPlaybackState"},
290 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, 343 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
@@ -483,6 +536,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) {
483 rb.Push(*layer_id); 536 rb.Push(*layer_id);
484} 537}
485 538
539void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) {
540 LOG_WARNING(Service_AM, "(STUBBED) called");
541
542 IPC::ResponseBuilder rb{ctx, 2};
543 rb.Push(this->EnsureBufferSharingEnabled());
544}
545
546void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) {
547 LOG_WARNING(Service_AM, "(STUBBED) called");
548
549 IPC::ResponseBuilder rb{ctx, 6};
550 rb.Push(this->EnsureBufferSharingEnabled());
551 rb.Push<s64>(system_shared_buffer_id);
552 rb.Push<s64>(system_shared_layer_id);
553}
554
555void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
556 LOG_WARNING(Service_AM, "(STUBBED) called");
557
558 IPC::ResponseBuilder rb{ctx, 4};
559 rb.Push(this->EnsureBufferSharingEnabled());
560 rb.Push<s64>(system_shared_buffer_id);
561}
562
563Result ISelfController::EnsureBufferSharingEnabled() {
564 if (buffer_sharing_enabled) {
565 return ResultSuccess;
566 }
567
568 if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) {
569 return VI::ResultOperationFailed;
570 }
571
572 const auto display_id = nvnflinger.OpenDisplay("Default");
573 const auto result = nvnflinger.GetSystemBufferManager().Initialize(
574 &system_shared_buffer_id, &system_shared_layer_id, *display_id);
575
576 if (result.IsSuccess()) {
577 buffer_sharing_enabled = true;
578 }
579
580 return result;
581}
582
486void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) { 583void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) {
487 LOG_WARNING(Service_AM, "(STUBBED) called"); 584 LOG_WARNING(Service_AM, "(STUBBED) called");
488 585
@@ -508,6 +605,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) {
508 rb.Push(ResultSuccess); 605 rb.Push(ResultSuccess);
509} 606}
510 607
608void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
609 LOG_WARNING(Service_AM, "(STUBBED) called");
610
611 IPC::ResponseBuilder rb{ctx, 2};
612 rb.Push(ResultSuccess);
613}
614
511void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { 615void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
512 IPC::RequestParser rp{ctx}; 616 IPC::RequestParser rp{ctx};
513 idle_time_detection_extension = rp.Pop<u32>(); 617 idle_time_detection_extension = rp.Pop<u32>();
@@ -676,9 +780,70 @@ void AppletMessageQueue::OperationModeChanged() {
676 on_operation_mode_changed->Signal(); 780 on_operation_mode_changed->Signal();
677} 781}
678 782
783ILockAccessor::ILockAccessor(Core::System& system_)
784 : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
785 // clang-format off
786 static const FunctionInfo functions[] = {
787 {1, &ILockAccessor::TryLock, "TryLock"},
788 {2, &ILockAccessor::Unlock, "Unlock"},
789 {3, &ILockAccessor::GetEvent, "GetEvent"},
790 {4,&ILockAccessor::IsLocked, "IsLocked"},
791 };
792 // clang-format on
793
794 RegisterHandlers(functions);
795
796 lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
797}
798
799ILockAccessor::~ILockAccessor() = default;
800
801void ILockAccessor::TryLock(HLERequestContext& ctx) {
802 IPC::RequestParser rp{ctx};
803 const auto return_handle = rp.Pop<bool>();
804
805 LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
806
807 // TODO: When return_handle is true this function should return the lock handle
808
809 is_locked = true;
810
811 IPC::ResponseBuilder rb{ctx, 3};
812 rb.Push(ResultSuccess);
813 rb.Push<u8>(is_locked);
814}
815
816void ILockAccessor::Unlock(HLERequestContext& ctx) {
817 LOG_INFO(Service_AM, "called");
818
819 is_locked = false;
820
821 IPC::ResponseBuilder rb{ctx, 2};
822 rb.Push(ResultSuccess);
823}
824
825void ILockAccessor::GetEvent(HLERequestContext& ctx) {
826 LOG_INFO(Service_AM, "called");
827
828 lock_event->Signal();
829
830 IPC::ResponseBuilder rb{ctx, 2, 1};
831 rb.Push(ResultSuccess);
832 rb.PushCopyObjects(lock_event->GetReadableEvent());
833}
834
835void ILockAccessor::IsLocked(HLERequestContext& ctx) {
836 LOG_INFO(Service_AM, "called");
837
838 IPC::ResponseBuilder rb{ctx, 2};
839 rb.Push(ResultSuccess);
840 rb.Push<u8>(is_locked);
841}
842
679ICommonStateGetter::ICommonStateGetter(Core::System& system_, 843ICommonStateGetter::ICommonStateGetter(Core::System& system_,
680 std::shared_ptr<AppletMessageQueue> msg_queue_) 844 std::shared_ptr<AppletMessageQueue> msg_queue_)
681 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { 845 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
846 service_context{system_, "ICommonStateGetter"} {
682 // clang-format off 847 // clang-format off
683 static const FunctionInfo functions[] = { 848 static const FunctionInfo functions[] = {
684 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, 849 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -691,14 +856,14 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
691 {7, nullptr, "GetCradleStatus"}, 856 {7, nullptr, "GetCradleStatus"},
692 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"}, 857 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
693 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, 858 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
694 {10, nullptr, "RequestToAcquireSleepLock"}, 859 {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"},
695 {11, nullptr, "ReleaseSleepLock"}, 860 {11, nullptr, "ReleaseSleepLock"},
696 {12, nullptr, "ReleaseSleepLockTransiently"}, 861 {12, nullptr, "ReleaseSleepLockTransiently"},
697 {13, nullptr, "GetAcquiredSleepLockEvent"}, 862 {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"},
698 {14, nullptr, "GetWakeupCount"}, 863 {14, nullptr, "GetWakeupCount"},
699 {20, nullptr, "PushToGeneralChannel"}, 864 {20, nullptr, "PushToGeneralChannel"},
700 {30, nullptr, "GetHomeButtonReaderLockAccessor"}, 865 {30, nullptr, "GetHomeButtonReaderLockAccessor"},
701 {31, nullptr, "GetReaderLockAccessorEx"}, 866 {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
702 {32, nullptr, "GetWriterLockAccessorEx"}, 867 {32, nullptr, "GetWriterLockAccessorEx"},
703 {40, nullptr, "GetCradleFwVersion"}, 868 {40, nullptr, "GetCradleFwVersion"},
704 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, 869 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -716,7 +881,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
716 {65, nullptr, "GetApplicationIdByContentActionName"}, 881 {65, nullptr, "GetApplicationIdByContentActionName"},
717 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, 882 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
718 {67, nullptr, "CancelCpuBoostMode"}, 883 {67, nullptr, "CancelCpuBoostMode"},
719 {68, nullptr, "GetBuiltInDisplayType"}, 884 {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
720 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"}, 885 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
721 {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, 886 {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
722 {91, nullptr, "GetCurrentPerformanceConfiguration"}, 887 {91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -724,7 +889,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
724 {110, nullptr, "OpenMyGpuErrorHandler"}, 889 {110, nullptr, "OpenMyGpuErrorHandler"},
725 {120, nullptr, "GetAppletLaunchedHistory"}, 890 {120, nullptr, "GetAppletLaunchedHistory"},
726 {200, nullptr, "GetOperationModeSystemInfo"}, 891 {200, nullptr, "GetOperationModeSystemInfo"},
727 {300, nullptr, "GetSettingsPlatformRegion"}, 892 {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
728 {400, nullptr, "ActivateMigrationService"}, 893 {400, nullptr, "ActivateMigrationService"},
729 {401, nullptr, "DeactivateMigrationService"}, 894 {401, nullptr, "DeactivateMigrationService"},
730 {500, nullptr, "DisableSleepTillShutdown"}, 895 {500, nullptr, "DisableSleepTillShutdown"},
@@ -736,6 +901,12 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
736 // clang-format on 901 // clang-format on
737 902
738 RegisterHandlers(functions); 903 RegisterHandlers(functions);
904
905 sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent");
906
907 // Configure applets to be in foreground state
908 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
909 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
739} 910}
740 911
741ICommonStateGetter::~ICommonStateGetter() = default; 912ICommonStateGetter::~ICommonStateGetter() = default;
@@ -781,6 +952,36 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) {
781 rb.Push(static_cast<u8>(FocusState::InFocus)); 952 rb.Push(static_cast<u8>(FocusState::InFocus));
782} 953}
783 954
955void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
956 LOG_WARNING(Service_AM, "(STUBBED) called");
957
958 // Sleep lock is acquired immediately.
959 sleep_lock_event->Signal();
960
961 IPC::ResponseBuilder rb{ctx, 2};
962 rb.Push(ResultSuccess);
963}
964
965void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
966 IPC::RequestParser rp{ctx};
967 const auto unknown = rp.Pop<u32>();
968
969 LOG_INFO(Service_AM, "called, unknown={}", unknown);
970
971 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
972
973 rb.Push(ResultSuccess);
974 rb.PushIpcInterface<ILockAccessor>(system);
975}
976
977void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
978 LOG_WARNING(Service_AM, "called");
979
980 IPC::ResponseBuilder rb{ctx, 2, 1};
981 rb.Push(ResultSuccess);
982 rb.PushCopyObjects(sleep_lock_event->GetReadableEvent());
983}
984
784void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) { 985void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) {
785 LOG_DEBUG(Service_AM, "called"); 986 LOG_DEBUG(Service_AM, "called");
786 987
@@ -857,6 +1058,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
857 apm_sys->SetCpuBoostMode(ctx); 1058 apm_sys->SetCpuBoostMode(ctx);
858} 1059}
859 1060
1061void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
1062 LOG_WARNING(Service_AM, "(STUBBED) called");
1063
1064 IPC::ResponseBuilder rb{ctx, 3};
1065 rb.Push(ResultSuccess);
1066 rb.Push(0);
1067}
1068
860void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) { 1069void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
861 IPC::RequestParser rp{ctx}; 1070 IPC::RequestParser rp{ctx};
862 const auto system_button{rp.PopEnum<SystemButtonType>()}; 1071 const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -867,6 +1076,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
867 rb.Push(ResultSuccess); 1076 rb.Push(ResultSuccess);
868} 1077}
869 1078
1079void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
1080 LOG_WARNING(Service_AM, "(STUBBED) called");
1081
1082 IPC::ResponseBuilder rb{ctx, 3};
1083 rb.Push(ResultSuccess);
1084 rb.PushEnum(SysPlatformRegion::Global);
1085}
1086
870void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled( 1087void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
871 HLERequestContext& ctx) { 1088 HLERequestContext& ctx) {
872 LOG_WARNING(Service_AM, "(STUBBED) called"); 1089 LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1324,18 +1541,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
1324 1541
1325ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) 1542ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1326 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { 1543 : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
1544 // clang-format off
1327 static const FunctionInfo functions[] = { 1545 static const FunctionInfo functions[] = {
1328 {0, nullptr, "PopInData"}, 1546 {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
1329 {1, nullptr, "PushOutData"}, 1547 {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
1330 {2, nullptr, "PopInteractiveInData"}, 1548 {2, nullptr, "PopInteractiveInData"},
1331 {3, nullptr, "PushInteractiveOutData"}, 1549 {3, nullptr, "PushInteractiveOutData"},
1332 {5, nullptr, "GetPopInDataEvent"}, 1550 {5, nullptr, "GetPopInDataEvent"},
1333 {6, nullptr, "GetPopInteractiveInDataEvent"}, 1551 {6, nullptr, "GetPopInteractiveInDataEvent"},
1334 {10, nullptr, "ExitProcessAndReturn"}, 1552 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
1335 {11, nullptr, "GetLibraryAppletInfo"}, 1553 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
1336 {12, nullptr, "GetMainAppletIdentityInfo"}, 1554 {12, nullptr, "GetMainAppletIdentityInfo"},
1337 {13, nullptr, "CanUseApplicationCore"}, 1555 {13, nullptr, "CanUseApplicationCore"},
1338 {14, nullptr, "GetCallerAppletIdentityInfo"}, 1556 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
1339 {15, nullptr, "GetMainAppletApplicationControlProperty"}, 1557 {15, nullptr, "GetMainAppletApplicationControlProperty"},
1340 {16, nullptr, "GetMainAppletStorageId"}, 1558 {16, nullptr, "GetMainAppletStorageId"},
1341 {17, nullptr, "GetCallerAppletIdentityInfoStack"}, 1559 {17, nullptr, "GetCallerAppletIdentityInfoStack"},
@@ -1355,16 +1573,225 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1355 {100, nullptr, "CreateGameMovieTrimmer"}, 1573 {100, nullptr, "CreateGameMovieTrimmer"},
1356 {101, nullptr, "ReserveResourceForMovieOperation"}, 1574 {101, nullptr, "ReserveResourceForMovieOperation"},
1357 {102, nullptr, "UnreserveResourceForMovieOperation"}, 1575 {102, nullptr, "UnreserveResourceForMovieOperation"},
1358 {110, nullptr, "GetMainAppletAvailableUsers"}, 1576 {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"},
1359 {120, nullptr, "GetLaunchStorageInfoForDebug"}, 1577 {120, nullptr, "GetLaunchStorageInfoForDebug"},
1360 {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, 1578 {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
1361 {140, nullptr, "SetApplicationMemoryReservation"}, 1579 {140, nullptr, "SetApplicationMemoryReservation"},
1362 {150, nullptr, "ShouldSetGpuTimeSliceManually"}, 1580 {150, nullptr, "ShouldSetGpuTimeSliceManually"},
1363 }; 1581 };
1582 // clang-format on
1364 RegisterHandlers(functions); 1583 RegisterHandlers(functions);
1584
1585 switch (system.GetAppletManager().GetCurrentAppletId()) {
1586 case Applets::AppletId::Cabinet:
1587 PushInShowCabinetData();
1588 break;
1589 case Applets::AppletId::MiiEdit:
1590 PushInShowMiiEditData();
1591 break;
1592 case Applets::AppletId::PhotoViewer:
1593 PushInShowAlbum();
1594 break;
1595 default:
1596 break;
1597 }
1365} 1598}
1366 1599
1367ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; 1600ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
1601void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
1602 LOG_INFO(Service_AM, "called");
1603
1604 if (queue_data.empty()) {
1605 IPC::ResponseBuilder rb{ctx, 2};
1606 rb.Push(ResultNoDataInChannel);
1607 return;
1608 }
1609
1610 auto data = queue_data.front();
1611 queue_data.pop_front();
1612
1613 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1614 rb.Push(ResultSuccess);
1615 rb.PushIpcInterface<IStorage>(system, std::move(data));
1616}
1617
1618void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
1619 LOG_WARNING(Service_AM, "(STUBBED) called");
1620
1621 IPC::ResponseBuilder rb{ctx, 2};
1622 rb.Push(ResultSuccess);
1623}
1624
1625void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
1626 LOG_WARNING(Service_AM, "(STUBBED) called");
1627
1628 system.Exit();
1629
1630 IPC::ResponseBuilder rb{ctx, 2};
1631 rb.Push(ResultSuccess);
1632}
1633
1634void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1635 struct LibraryAppletInfo {
1636 Applets::AppletId applet_id;
1637 Applets::LibraryAppletMode library_applet_mode;
1638 };
1639
1640 LOG_WARNING(Service_AM, "(STUBBED) called");
1641
1642 const LibraryAppletInfo applet_info{
1643 .applet_id = system.GetAppletManager().GetCurrentAppletId(),
1644 .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
1645 };
1646
1647 IPC::ResponseBuilder rb{ctx, 4};
1648 rb.Push(ResultSuccess);
1649 rb.PushRaw(applet_info);
1650}
1651
1652void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
1653 struct AppletIdentityInfo {
1654 Applets::AppletId applet_id;
1655 INSERT_PADDING_BYTES(0x4);
1656 u64 application_id;
1657 };
1658
1659 LOG_WARNING(Service_AM, "(STUBBED) called");
1660
1661 const AppletIdentityInfo applet_info{
1662 .applet_id = Applets::AppletId::QLaunch,
1663 .application_id = 0x0100000000001000ull,
1664 };
1665
1666 IPC::ResponseBuilder rb{ctx, 6};
1667 rb.Push(ResultSuccess);
1668 rb.PushRaw(applet_info);
1669}
1670
1671void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
1672 const Service::Account::ProfileManager manager{};
1673 bool is_empty{true};
1674 s32 user_count{-1};
1675
1676 LOG_INFO(Service_AM, "called");
1677
1678 if (manager.GetUserCount() > 0) {
1679 is_empty = false;
1680 user_count = static_cast<s32>(manager.GetUserCount());
1681 ctx.WriteBuffer(manager.GetAllUsers());
1682 }
1683
1684 IPC::ResponseBuilder rb{ctx, 4};
1685 rb.Push(ResultSuccess);
1686 rb.Push<u8>(is_empty);
1687 rb.Push(user_count);
1688}
1689
1690void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1691 const Applets::CommonArguments arguments{
1692 .arguments_version = Applets::CommonArgumentVersion::Version3,
1693 .size = Applets::CommonArgumentSize::Version3,
1694 .library_version = 1,
1695 .theme_color = Applets::ThemeColor::BasicBlack,
1696 .play_startup_sound = true,
1697 .system_tick = system.CoreTiming().GetClockTicks(),
1698 };
1699
1700 std::vector<u8> argument_data(sizeof(arguments));
1701 std::vector<u8> settings_data{2};
1702 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1703 queue_data.emplace_back(std::move(argument_data));
1704 queue_data.emplace_back(std::move(settings_data));
1705}
1706
1707void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1708 const Applets::CommonArguments arguments{
1709 .arguments_version = Applets::CommonArgumentVersion::Version3,
1710 .size = Applets::CommonArgumentSize::Version3,
1711 .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1),
1712 .theme_color = Applets::ThemeColor::BasicBlack,
1713 .play_startup_sound = true,
1714 .system_tick = system.CoreTiming().GetClockTicks(),
1715 };
1716
1717 const Applets::StartParamForAmiiboSettings amiibo_settings{
1718 .param_1 = 0,
1719 .applet_mode = system.GetAppletManager().GetCabinetMode(),
1720 .flags = Applets::CabinetFlags::None,
1721 .amiibo_settings_1 = 0,
1722 .device_handle = 0,
1723 .tag_info{},
1724 .register_info{},
1725 .amiibo_settings_3{},
1726 };
1727
1728 std::vector<u8> argument_data(sizeof(arguments));
1729 std::vector<u8> settings_data(sizeof(amiibo_settings));
1730 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1731 std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings));
1732 queue_data.emplace_back(std::move(argument_data));
1733 queue_data.emplace_back(std::move(settings_data));
1734}
1735
1736void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
1737 struct MiiEditV3 {
1738 Applets::MiiEditAppletInputCommon common;
1739 Applets::MiiEditAppletInputV3 input;
1740 };
1741 static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
1742
1743 MiiEditV3 mii_arguments{
1744 .common =
1745 {
1746 .version = Applets::MiiEditAppletVersion::Version3,
1747 .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
1748 },
1749 .input{},
1750 };
1751
1752 std::vector<u8> argument_data(sizeof(mii_arguments));
1753 std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
1754
1755 queue_data.emplace_back(std::move(argument_data));
1756}
1757
1758IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
1759 : ServiceFramework{system_, "IAppletCommonFunctions"} {
1760 // clang-format off
1761 static const FunctionInfo functions[] = {
1762 {0, nullptr, "SetTerminateResult"},
1763 {10, nullptr, "ReadThemeStorage"},
1764 {11, nullptr, "WriteThemeStorage"},
1765 {20, nullptr, "PushToAppletBoundChannel"},
1766 {21, nullptr, "TryPopFromAppletBoundChannel"},
1767 {40, nullptr, "GetDisplayLogicalResolution"},
1768 {42, nullptr, "SetDisplayMagnification"},
1769 {50, nullptr, "SetHomeButtonDoubleClickEnabled"},
1770 {51, nullptr, "GetHomeButtonDoubleClickEnabled"},
1771 {52, nullptr, "IsHomeButtonShortPressedBlocked"},
1772 {60, nullptr, "IsVrModeCurtainRequired"},
1773 {61, nullptr, "IsSleepRequiredByHighTemperature"},
1774 {62, nullptr, "IsSleepRequiredByLowBattery"},
1775 {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
1776 {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
1777 {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
1778 {90, nullptr, "OpenNamedChannelAsParent"},
1779 {91, nullptr, "OpenNamedChannelAsChild"},
1780 {100, nullptr, "SetApplicationCoreUsageMode"},
1781 };
1782 // clang-format on
1783
1784 RegisterHandlers(functions);
1785}
1786
1787IAppletCommonFunctions::~IAppletCommonFunctions() = default;
1788
1789void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
1790 LOG_WARNING(Service_AM, "(STUBBED) called");
1791
1792 IPC::ResponseBuilder rb{ctx, 2};
1793 rb.Push(ResultSuccess);
1794}
1368 1795
1369IApplicationFunctions::IApplicationFunctions(Core::System& system_) 1796IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1370 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, 1797 : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@@ -1941,9 +2368,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
1941 2368
1942void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) { 2369void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
1943 auto message_queue = std::make_shared<AppletMessageQueue>(system); 2370 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); 2371 auto server_manager = std::make_unique<ServerManager>(system);
1948 2372
1949 server_manager->RegisterNamedService( 2373 server_manager->RegisterNamedService(
@@ -2049,8 +2473,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2049 : ServiceFramework{system_, "IProcessWindingController"} { 2473 : ServiceFramework{system_, "IProcessWindingController"} {
2050 // clang-format off 2474 // clang-format off
2051 static const FunctionInfo functions[] = { 2475 static const FunctionInfo functions[] = {
2052 {0, nullptr, "GetLaunchReason"}, 2476 {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
2053 {11, nullptr, "OpenCallingLibraryApplet"}, 2477 {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
2054 {21, nullptr, "PushContext"}, 2478 {21, nullptr, "PushContext"},
2055 {22, nullptr, "PopContext"}, 2479 {22, nullptr, "PopContext"},
2056 {23, nullptr, "CancelWindingReservation"}, 2480 {23, nullptr, "CancelWindingReservation"},
@@ -2064,4 +2488,47 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
2064} 2488}
2065 2489
2066IProcessWindingController::~IProcessWindingController() = default; 2490IProcessWindingController::~IProcessWindingController() = default;
2491
2492void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
2493 LOG_WARNING(Service_AM, "(STUBBED) called");
2494
2495 struct AppletProcessLaunchReason {
2496 u8 flag;
2497 INSERT_PADDING_BYTES(3);
2498 };
2499 static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
2500 "AppletProcessLaunchReason is an invalid size");
2501
2502 AppletProcessLaunchReason reason{
2503 .flag = 0,
2504 };
2505
2506 IPC::ResponseBuilder rb{ctx, 3};
2507 rb.Push(ResultSuccess);
2508 rb.PushRaw(reason);
2509}
2510
2511void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
2512 const auto applet_id = system.GetAppletManager().GetCurrentAppletId();
2513 const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
2514
2515 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
2516 applet_mode);
2517
2518 const auto& applet_manager{system.GetAppletManager()};
2519 const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
2520
2521 if (applet == nullptr) {
2522 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
2523
2524 IPC::ResponseBuilder rb{ctx, 2};
2525 rb.Push(ResultUnknown);
2526 return;
2527 }
2528
2529 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
2530 rb.Push(ResultSuccess);
2531 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
2532}
2533
2067} // namespace Service::AM 2534} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f86841c60..64b3f3fe2 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -120,6 +120,14 @@ 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 AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
129 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
130 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
123}; 131};
124 132
125class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { 133class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -147,9 +155,13 @@ private:
147 void SetRestartMessageEnabled(HLERequestContext& ctx); 155 void SetRestartMessageEnabled(HLERequestContext& ctx);
148 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx); 156 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx);
149 void SetAlbumImageOrientation(HLERequestContext& ctx); 157 void SetAlbumImageOrientation(HLERequestContext& ctx);
158 void IsSystemBufferSharingEnabled(HLERequestContext& ctx);
159 void GetSystemSharedBufferHandle(HLERequestContext& ctx);
160 void GetSystemSharedLayerHandle(HLERequestContext& ctx);
150 void CreateManagedDisplayLayer(HLERequestContext& ctx); 161 void CreateManagedDisplayLayer(HLERequestContext& ctx);
151 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); 162 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
152 void SetHandlesRequestToDisplay(HLERequestContext& ctx); 163 void SetHandlesRequestToDisplay(HLERequestContext& ctx);
164 void ApproveToDisplay(HLERequestContext& ctx);
153 void SetIdleTimeDetectionExtension(HLERequestContext& ctx); 165 void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
154 void GetIdleTimeDetectionExtension(HLERequestContext& ctx); 166 void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
155 void ReportUserIsActive(HLERequestContext& ctx); 167 void ReportUserIsActive(HLERequestContext& ctx);
@@ -161,6 +173,8 @@ private:
161 void SaveCurrentScreenshot(HLERequestContext& ctx); 173 void SaveCurrentScreenshot(HLERequestContext& ctx);
162 void SetRecordVolumeMuted(HLERequestContext& ctx); 174 void SetRecordVolumeMuted(HLERequestContext& ctx);
163 175
176 Result EnsureBufferSharingEnabled();
177
164 enum class ScreenshotPermission : u32 { 178 enum class ScreenshotPermission : u32 {
165 Inherit = 0, 179 Inherit = 0,
166 Enable = 1, 180 Enable = 1,
@@ -176,10 +190,30 @@ private:
176 190
177 u32 idle_time_detection_extension = 0; 191 u32 idle_time_detection_extension = 0;
178 u64 num_fatal_sections_entered = 0; 192 u64 num_fatal_sections_entered = 0;
193 u64 system_shared_buffer_id = 0;
194 u64 system_shared_layer_id = 0;
179 bool is_auto_sleep_disabled = false; 195 bool is_auto_sleep_disabled = false;
196 bool buffer_sharing_enabled = false;
180 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; 197 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
181}; 198};
182 199
200class ILockAccessor final : public ServiceFramework<ILockAccessor> {
201public:
202 explicit ILockAccessor(Core::System& system_);
203 ~ILockAccessor() override;
204
205private:
206 void TryLock(HLERequestContext& ctx);
207 void Unlock(HLERequestContext& ctx);
208 void GetEvent(HLERequestContext& ctx);
209 void IsLocked(HLERequestContext& ctx);
210
211 bool is_locked{};
212
213 Kernel::KEvent* lock_event;
214 KernelHelpers::ServiceContext service_context;
215};
216
183class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 217class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
184public: 218public:
185 explicit ICommonStateGetter(Core::System& system_, 219 explicit ICommonStateGetter(Core::System& system_,
@@ -212,9 +246,17 @@ private:
212 CaptureButtonLongPressing, 246 CaptureButtonLongPressing,
213 }; 247 };
214 248
249 enum class SysPlatformRegion : s32 {
250 Global = 1,
251 Terra = 2,
252 };
253
215 void GetEventHandle(HLERequestContext& ctx); 254 void GetEventHandle(HLERequestContext& ctx);
216 void ReceiveMessage(HLERequestContext& ctx); 255 void ReceiveMessage(HLERequestContext& ctx);
217 void GetCurrentFocusState(HLERequestContext& ctx); 256 void GetCurrentFocusState(HLERequestContext& ctx);
257 void RequestToAcquireSleepLock(HLERequestContext& ctx);
258 void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
259 void GetReaderLockAccessorEx(HLERequestContext& ctx);
218 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx); 260 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
219 void GetOperationMode(HLERequestContext& ctx); 261 void GetOperationMode(HLERequestContext& ctx);
220 void GetPerformanceMode(HLERequestContext& ctx); 262 void GetPerformanceMode(HLERequestContext& ctx);
@@ -226,11 +268,15 @@ private:
226 void EndVrModeEx(HLERequestContext& ctx); 268 void EndVrModeEx(HLERequestContext& ctx);
227 void GetDefaultDisplayResolution(HLERequestContext& ctx); 269 void GetDefaultDisplayResolution(HLERequestContext& ctx);
228 void SetCpuBoostMode(HLERequestContext& ctx); 270 void SetCpuBoostMode(HLERequestContext& ctx);
271 void GetBuiltInDisplayType(HLERequestContext& ctx);
229 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 272 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
273 void GetSettingsPlatformRegion(HLERequestContext& ctx);
230 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 274 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
231 275
232 std::shared_ptr<AppletMessageQueue> msg_queue; 276 std::shared_ptr<AppletMessageQueue> msg_queue;
233 bool vr_mode_state{}; 277 bool vr_mode_state{};
278 Kernel::KEvent* sleep_lock_event;
279 KernelHelpers::ServiceContext service_context;
234}; 280};
235 281
236class IStorageImpl { 282class IStorageImpl {
@@ -294,6 +340,29 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
294public: 340public:
295 explicit ILibraryAppletSelfAccessor(Core::System& system_); 341 explicit ILibraryAppletSelfAccessor(Core::System& system_);
296 ~ILibraryAppletSelfAccessor() override; 342 ~ILibraryAppletSelfAccessor() override;
343
344private:
345 void PopInData(HLERequestContext& ctx);
346 void PushOutData(HLERequestContext& ctx);
347 void GetLibraryAppletInfo(HLERequestContext& ctx);
348 void ExitProcessAndReturn(HLERequestContext& ctx);
349 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
350 void GetMainAppletAvailableUsers(HLERequestContext& ctx);
351
352 void PushInShowAlbum();
353 void PushInShowCabinetData();
354 void PushInShowMiiEditData();
355
356 std::deque<std::vector<u8>> queue_data;
357};
358
359class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
360public:
361 explicit IAppletCommonFunctions(Core::System& system_);
362 ~IAppletCommonFunctions() override;
363
364private:
365 void SetCpuBoostRequestPriority(HLERequestContext& ctx);
297}; 366};
298 367
299class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 368class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -378,6 +447,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
378public: 447public:
379 explicit IProcessWindingController(Core::System& system_); 448 explicit IProcessWindingController(Core::System& system_);
380 ~IProcessWindingController() override; 449 ~IProcessWindingController() override;
450
451private:
452 void GetLaunchReason(HLERequestContext& ctx);
453 void OpenCallingLibraryApplet(HLERequestContext& ctx);
381}; 454};
382 455
383void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system); 456void 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..31dd98140 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>(system);
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..9925720a3 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, 4};
132 rb.Push(result);
133 rb.Push<u64>(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..2b4e3f076
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,386 @@
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/core.h"
12#include "core/hle/service/caps/caps_manager.h"
13#include "core/hle/service/caps/caps_result.h"
14#include "core/hle/service/time/time_manager.h"
15#include "core/hle/service/time/time_zone_content_manager.h"
16
17namespace Service::Capture {
18
19AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
20
21AlbumManager::~AlbumManager() = default;
22
23Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
24 if (file_id.storage > AlbumStorage::Sd) {
25 return ResultInvalidStorage;
26 }
27
28 if (!is_mounted) {
29 return ResultIsNotMounted;
30 }
31
32 std::filesystem::path path;
33 const auto result = GetFile(path, file_id);
34
35 if (result.IsError()) {
36 return result;
37 }
38
39 if (!Common::FS::RemoveFile(path)) {
40 return ResultFileNotFound;
41 }
42
43 return ResultSuccess;
44}
45
46Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
47 if (storage > AlbumStorage::Sd) {
48 return ResultInvalidStorage;
49 }
50
51 is_mounted = true;
52
53 if (storage == AlbumStorage::Sd) {
54 FindScreenshots();
55 }
56
57 return is_mounted ? ResultSuccess : ResultIsNotMounted;
58}
59
60Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
61 u8 flags) const {
62 if (storage > AlbumStorage::Sd) {
63 return ResultInvalidStorage;
64 }
65
66 if (!is_mounted) {
67 return ResultIsNotMounted;
68 }
69
70 for (auto& [file_id, path] : album_files) {
71 if (file_id.storage != storage) {
72 continue;
73 }
74 if (out_entries.size() >= SdAlbumFileLimit) {
75 break;
76 }
77
78 const auto entry_size = Common::FS::GetSize(path);
79 out_entries.push_back({
80 .entry_size = entry_size,
81 .file_id = file_id,
82 });
83 }
84
85 return ResultSuccess;
86}
87
88Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
89 ContentType contex_type, s64 start_posix_time,
90 s64 end_posix_time, u64 aruid) const {
91 if (!is_mounted) {
92 return ResultIsNotMounted;
93 }
94
95 std::vector<ApplicationAlbumEntry> album_entries;
96 const auto start_date = ConvertToAlbumDateTime(start_posix_time);
97 const auto end_date = ConvertToAlbumDateTime(end_posix_time);
98 const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
99
100 if (result.IsError()) {
101 return result;
102 }
103
104 for (const auto& album_entry : album_entries) {
105 ApplicationAlbumFileEntry entry{
106 .entry = album_entry,
107 .datetime = album_entry.datetime,
108 .unknown = {},
109 };
110 out_entries.push_back(entry);
111 }
112
113 return ResultSuccess;
114}
115
116Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
117 ContentType contex_type, AlbumFileDateTime start_date,
118 AlbumFileDateTime end_date, u64 aruid) const {
119 if (!is_mounted) {
120 return ResultIsNotMounted;
121 }
122
123 for (auto& [file_id, path] : album_files) {
124 if (file_id.type != contex_type) {
125 continue;
126 }
127 if (file_id.date > start_date) {
128 continue;
129 }
130 if (file_id.date < end_date) {
131 continue;
132 }
133 if (out_entries.size() >= SdAlbumFileLimit) {
134 break;
135 }
136
137 const auto entry_size = Common::FS::GetSize(path);
138 ApplicationAlbumEntry entry{
139 .size = entry_size,
140 .hash{},
141 .datetime = file_id.date,
142 .storage = file_id.storage,
143 .content = contex_type,
144 .unknown = 1,
145 };
146 out_entries.push_back(entry);
147 }
148
149 return ResultSuccess;
150}
151
152Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
153 out_is_autosaving = false;
154 return ResultSuccess;
155}
156
157Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
158 std::vector<u8>& out_image,
159 const AlbumFileId& file_id,
160 const ScreenShotDecodeOption& decoder_options) const {
161 if (file_id.storage > AlbumStorage::Sd) {
162 return ResultInvalidStorage;
163 }
164
165 if (!is_mounted) {
166 return ResultIsNotMounted;
167 }
168
169 out_image_output = {
170 .width = 1280,
171 .height = 720,
172 .attribute =
173 {
174 .unknown_0{},
175 .orientation = AlbumImageOrientation::None,
176 .unknown_1{},
177 .unknown_2{},
178 },
179 };
180
181 std::filesystem::path path;
182 const auto result = GetFile(path, file_id);
183
184 if (result.IsError()) {
185 return result;
186 }
187
188 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
189
190 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
191 +static_cast<int>(out_image_output.height), decoder_options.flags);
192}
193
194Result AlbumManager::LoadAlbumScreenShotThumbnail(
195 LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
196 const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
197 if (file_id.storage > AlbumStorage::Sd) {
198 return ResultInvalidStorage;
199 }
200
201 if (!is_mounted) {
202 return ResultIsNotMounted;
203 }
204
205 out_image_output = {
206 .width = 320,
207 .height = 180,
208 .attribute =
209 {
210 .unknown_0{},
211 .orientation = AlbumImageOrientation::None,
212 .unknown_1{},
213 .unknown_2{},
214 },
215 };
216
217 std::filesystem::path path;
218 const auto result = GetFile(path, file_id);
219
220 if (result.IsError()) {
221 return result;
222 }
223
224 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
225
226 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
227 +static_cast<int>(out_image_output.height), decoder_options.flags);
228}
229
230Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
231 const auto file = album_files.find(file_id);
232
233 if (file == album_files.end()) {
234 return ResultFileNotFound;
235 }
236
237 out_path = file->second;
238 return ResultSuccess;
239}
240
241void AlbumManager::FindScreenshots() {
242 is_mounted = false;
243 album_files.clear();
244
245 // TODO: Swap this with a blocking operation.
246 const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
247 Common::FS::IterateDirEntries(
248 screenshots_dir,
249 [this](const std::filesystem::path& full_path) {
250 AlbumEntry entry;
251 if (GetAlbumEntry(entry, full_path).IsError()) {
252 return true;
253 }
254 while (album_files.contains(entry.file_id)) {
255 if (++entry.file_id.date.unique_id == 0) {
256 break;
257 }
258 }
259 album_files[entry.file_id] = full_path;
260 return true;
261 },
262 Common::FS::DirEntryFilter::File);
263
264 is_mounted = true;
265}
266
267Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
268 std::istringstream line_stream(path.filename().string());
269 std::string date;
270 std::string application;
271 std::string time;
272
273 // Parse filename to obtain entry properties
274 std::getline(line_stream, application, '_');
275 std::getline(line_stream, date, '_');
276 std::getline(line_stream, time, '_');
277
278 std::istringstream date_stream(date);
279 std::istringstream time_stream(time);
280 std::string year;
281 std::string month;
282 std::string day;
283 std::string hour;
284 std::string minute;
285 std::string second;
286
287 std::getline(date_stream, year, '-');
288 std::getline(date_stream, month, '-');
289 std::getline(date_stream, day, '-');
290
291 std::getline(time_stream, hour, '-');
292 std::getline(time_stream, minute, '-');
293 std::getline(time_stream, second, '-');
294
295 try {
296 out_entry = {
297 .entry_size = 1,
298 .file_id{
299 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
300 .date =
301 {
302 .year = static_cast<s16>(std::stoi(year)),
303 .month = static_cast<s8>(std::stoi(month)),
304 .day = static_cast<s8>(std::stoi(day)),
305 .hour = static_cast<s8>(std::stoi(hour)),
306 .minute = static_cast<s8>(std::stoi(minute)),
307 .second = static_cast<s8>(std::stoi(second)),
308 .unique_id = 0,
309 },
310 .storage = AlbumStorage::Sd,
311 .type = ContentType::Screenshot,
312 .unknown = 1,
313 },
314 };
315 } catch (const std::invalid_argument&) {
316 return ResultUnknown;
317 } catch (const std::out_of_range&) {
318 return ResultUnknown;
319 } catch (const std::exception&) {
320 return ResultUnknown;
321 }
322
323 return ResultSuccess;
324}
325
326Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
327 int width, int height, ScreenShotDecoderFlag flag) const {
328 if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
329 return ResultUnknown;
330 }
331
332 const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
333 Common::FS::FileType::BinaryFile};
334
335 std::vector<u8> raw_file(db_file.GetSize());
336 if (db_file.Read(raw_file) != raw_file.size()) {
337 return ResultUnknown;
338 }
339
340 int filter_flag = STBIR_FILTER_DEFAULT;
341 int original_width, original_height, color_channels;
342 const auto dbi_image =
343 stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
344 &original_height, &color_channels, STBI_rgb_alpha);
345
346 if (dbi_image == nullptr) {
347 return ResultUnknown;
348 }
349
350 switch (flag) {
351 case ScreenShotDecoderFlag::EnableFancyUpsampling:
352 filter_flag = STBIR_FILTER_TRIANGLE;
353 break;
354 case ScreenShotDecoderFlag::EnableBlockSmoothing:
355 filter_flag = STBIR_FILTER_BOX;
356 break;
357 default:
358 filter_flag = STBIR_FILTER_DEFAULT;
359 break;
360 }
361
362 stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
363 height, 0, STBI_rgb_alpha, 3, filter_flag);
364
365 return ResultSuccess;
366}
367
368AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
369 Time::TimeZone::CalendarInfo calendar_date{};
370 const auto& time_zone_manager =
371 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
372
373 time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
374
375 return {
376 .year = calendar_date.time.year,
377 .month = calendar_date.time.month,
378 .day = calendar_date.time.day,
379 .hour = calendar_date.time.hour,
380 .minute = calendar_date.time.minute,
381 .second = calendar_date.time.second,
382 .unique_id = 0,
383 };
384}
385
386} // 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..f65eb12c1
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,79 @@
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(Core::System& system_);
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, s64 start_posix_time, s64 end_posix_time,
49 u64 aruid) const;
50 Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
51 ContentType contex_type, AlbumFileDateTime start_date,
52 AlbumFileDateTime end_date, u64 aruid) const;
53 Result GetAutoSavingStorage(bool& out_is_autosaving) const;
54 Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
55 std::vector<u8>& out_image, const AlbumFileId& file_id,
56 const ScreenShotDecodeOption& decoder_options) const;
57 Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
58 std::vector<u8>& out_image, const AlbumFileId& file_id,
59 const ScreenShotDecodeOption& decoder_options) const;
60
61private:
62 static constexpr std::size_t NandAlbumFileLimit = 1000;
63 static constexpr std::size_t SdAlbumFileLimit = 10000;
64
65 void FindScreenshots();
66 Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
67 Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
68 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
69 int height, ScreenShotDecoderFlag flag) const;
70
71 AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
72
73 bool is_mounted{};
74 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
75
76 Core::System& system;
77};
78
79} // 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..7fd357954
--- /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 s16 year{};
45 s8 month{};
46 s8 day{};
47 s8 hour{};
48 s8 minute{};
49 s8 second{};
50 s8 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..b6b33fb2f 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,37 +48,89 @@ 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 struct Parameters {
73 const auto content_type{rp.PopEnum<ContentType>()}; 54 ContentType content_type;
74 const auto start_posix_time{rp.Pop<s64>()}; 55 INSERT_PADDING_BYTES(7);
75 const auto end_posix_time{rp.Pop<s64>()}; 56 s64 start_posix_time;
76 const auto applet_resource_user_id{rp.Pop<u64>()}; 57 s64 end_posix_time;
58 u64 applet_resource_user_id;
59 };
60 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
77 61
78 // TODO: Update this when we implement the album. 62 const auto parameters{rp.PopRaw<Parameters>()};
79 // Currently we do not have a method of accessing album entries, set this to 0 for now.
80 constexpr u32 total_entries_1{};
81 constexpr u32 total_entries_2{};
82 63
83 LOG_WARNING( 64 LOG_WARNING(Service_Capture,
84 Service_Capture, 65 "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
85 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " 66 "applet_resource_user_id={}",
86 "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}", 67 parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
87 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, 68 parameters.applet_resource_user_id);
88 total_entries_1, total_entries_2); 69
70 Result result = ResultSuccess;
71
72 if (result.IsSuccess()) {
73 result = manager->IsAlbumMounted(AlbumStorage::Sd);
74 }
75
76 std::vector<ApplicationAlbumFileEntry> entries;
77 if (result.IsSuccess()) {
78 result = manager->GetAlbumFileList(entries, parameters.content_type,
79 parameters.start_posix_time, parameters.end_posix_time,
80 parameters.applet_resource_user_id);
81 }
82
83 if (!entries.empty()) {
84 ctx.WriteBuffer(entries);
85 }
89 86
90 IPC::ResponseBuilder rb{ctx, 4}; 87 IPC::ResponseBuilder rb{ctx, 4};
91 rb.Push(ResultSuccess); 88 rb.Push(result);
92 rb.Push(total_entries_1); 89 rb.Push<u64>(entries.size());
93 rb.Push(total_entries_2);
94} 90}
95 91
96void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { 92void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
97 GetAlbumContentsFileListForApplication(ctx); 93 IPC::RequestParser rp{ctx};
94 struct Parameters {
95 ContentType content_type;
96 INSERT_PADDING_BYTES(1);
97 AlbumFileDateTime start_date_time;
98 AlbumFileDateTime end_date_time;
99 INSERT_PADDING_BYTES(6);
100 u64 applet_resource_user_id;
101 };
102 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
103
104 const auto parameters{rp.PopRaw<Parameters>()};
105
106 LOG_WARNING(Service_Capture,
107 "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
108 "end_date={}/{}/{}, applet_resource_user_id={}",
109 parameters.content_type, parameters.start_date_time.year,
110 parameters.start_date_time.month, parameters.start_date_time.day,
111 parameters.end_date_time.year, parameters.end_date_time.month,
112 parameters.end_date_time.day, parameters.applet_resource_user_id);
113
114 Result result = ResultSuccess;
115
116 if (result.IsSuccess()) {
117 result = manager->IsAlbumMounted(AlbumStorage::Sd);
118 }
119
120 std::vector<ApplicationAlbumEntry> entries;
121 if (result.IsSuccess()) {
122 result =
123 manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
124 parameters.end_date_time, parameters.applet_resource_user_id);
125 }
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result);
133 rb.Push<u64>(entries.size());
98} 134}
99 135
100} // namespace Service::Capture 136} // 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/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
156 156
157 bool LoadNRO(std::span<const u8> data) { 157 bool LoadNRO(std::span<const u8> data) {
158 local_memory.clear(); 158 local_memory.clear();
159
160 relocbase = local_memory.size();
159 local_memory.insert(local_memory.end(), data.begin(), data.end()); 161 local_memory.insert(local_memory.end(), data.begin(), data.end());
160 162
161 if (FixupRelocations()) { 163 if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
181 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html 183 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
182 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html 184 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
183 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; 185 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
184 VAddr rela_dyn = 0; 186 VAddr rela_dyn = 0, relr_dyn = 0;
185 size_t num_rela = 0; 187 size_t num_rela = 0, num_relr = 0;
186 while (true) { 188 while (true) {
187 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; 189 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
188 dynamic_offset += sizeof(Elf64_Dyn); 190 dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
196 if (dyn.d_tag == ElfDtRelasz) { 198 if (dyn.d_tag == ElfDtRelasz) {
197 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); 199 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
198 } 200 }
201 if (dyn.d_tag == ElfDtRelr) {
202 relr_dyn = dyn.d_un.d_ptr;
203 }
204 if (dyn.d_tag == ElfDtRelrsz) {
205 num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
206 }
199 } 207 }
200 208
201 for (size_t i = 0; i < num_rela; i++) { 209 for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
207 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); 215 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
208 } 216 }
209 217
218 VAddr relr_where = 0;
219 for (size_t i = 0; i < num_relr; i++) {
220 const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
221 const auto incr{[&](VAddr where) {
222 callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
223 }};
224
225 if ((relr & 1) == 0) {
226 // where pointer
227 relr_where = relocbase + relr;
228 incr(relr_where);
229 relr_where += sizeof(Elf64_Addr);
230 } else {
231 // bitmap
232 for (int bit = 1; bit < 64; bit++) {
233 if ((relr & (1ULL << bit)) != 0) {
234 incr(relr_where + i * sizeof(Elf64_Addr));
235 }
236 }
237 relr_where += 63 * sizeof(Elf64_Addr);
238 }
239 }
240
210 return true; 241 return true;
211 } 242 }
212 243
@@ -313,6 +344,7 @@ public:
313 Core::Memory::Memory& memory; 344 Core::Memory::Memory& memory;
314 VAddr top_of_stack; 345 VAddr top_of_stack;
315 VAddr heap_pointer; 346 VAddr heap_pointer;
347 VAddr relocbase;
316}; 348};
317 349
318void DynarmicCallbacks64::CallSVC(u32 swi) { 350void DynarmicCallbacks64::CallSVC(u32 swi) {
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/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 808b21069..77db60e92 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host,
338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { 338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); 339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
340 PreSetController(identifier); 340 PreSetController(identifier);
341 PreSetMotion(identifier, 0);
341 } 342 }
342} 343}
343 344
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..9b2698fad 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>
@@ -538,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
538 it++; 544 it++;
539 } 545 }
540 546
541 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; 547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
542 u64 total_size_bytes = 0; 548 u64 total_size_bytes = 0;
543 u64 largest_copy = 0; 549 u64 largest_copy = 0;
544 for (const IntervalSet& intervals : committed_ranges) { 550 for (const IntervalSet& intervals : committed_ranges) {
@@ -908,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
908 914
909 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
910 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917
918 if (is_written) {
919 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
920 }
921
911 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 922 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
912 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); 923 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
913 ++binding_index; 924 ++binding_index;
@@ -925,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
925 const u32 size = binding.size; 936 const u32 size = binding.size;
926 SynchronizeBuffer(buffer, binding.cpu_addr, size); 937 SynchronizeBuffer(buffer, binding.cpu_addr, size);
927 938
939 const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
940 if (is_written) {
941 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
942 }
943
928 const u32 offset = buffer.Offset(binding.cpu_addr); 944 const u32 offset = buffer.Offset(binding.cpu_addr);
929 const PixelFormat format = binding.format; 945 const PixelFormat format = binding.format;
930 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -956,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
956 const u32 size = binding.size; 972 const u32 size = binding.size;
957 SynchronizeBuffer(buffer, binding.cpu_addr, size); 973 SynchronizeBuffer(buffer, binding.cpu_addr, size);
958 974
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976
959 const u32 offset = buffer.Offset(binding.cpu_addr); 977 const u32 offset = buffer.Offset(binding.cpu_addr);
960 host_bindings.buffers.push_back(&buffer); 978 host_bindings.buffers.push_back(&buffer);
961 host_bindings.offsets.push_back(offset); 979 host_bindings.offsets.push_back(offset);
@@ -1005,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1005 const u32 offset = buffer.Offset(binding.cpu_addr); 1023 const u32 offset = buffer.Offset(binding.cpu_addr);
1006 const bool is_written = 1024 const bool is_written =
1007 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026
1027 if (is_written) {
1028 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1029 }
1030
1008 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 1031 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
1009 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); 1032 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
1010 ++binding_index; 1033 ++binding_index;
@@ -1022,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1022 const u32 size = binding.size; 1045 const u32 size = binding.size;
1023 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1046 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1024 1047
1048 const bool is_written =
1049 ((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
1050 if (is_written) {
1051 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1052 }
1053
1025 const u32 offset = buffer.Offset(binding.cpu_addr); 1054 const u32 offset = buffer.Offset(binding.cpu_addr);
1026 const PixelFormat format = binding.format; 1055 const PixelFormat format = binding.format;
1027 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -1195,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1195 1224
1196template <class P> 1225template <class P>
1197void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1226void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1198 const u32 written_mask = channel_state->written_storage_buffers[stage];
1199 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { 1227 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1200 // Resolve buffer 1228 // Resolve buffer
1201 Binding& binding = channel_state->storage_buffers[stage][index]; 1229 Binding& binding = channel_state->storage_buffers[stage][index];
1202 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1230 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1203 binding.buffer_id = buffer_id; 1231 binding.buffer_id = buffer_id;
1204 // Mark buffer as written if needed
1205 if (((written_mask >> index) & 1) != 0) {
1206 MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
1207 }
1208 }); 1232 });
1209} 1233}
1210 1234
@@ -1213,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1213 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { 1237 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1214 Binding& binding = channel_state->texture_buffers[stage][index]; 1238 Binding& binding = channel_state->texture_buffers[stage][index];
1215 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1239 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1216 // Mark buffer as written if needed
1217 if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
1218 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1219 }
1220 }); 1240 });
1221} 1241}
1222 1242
@@ -1246,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1246 .size = size, 1266 .size = size,
1247 .buffer_id = buffer_id, 1267 .buffer_id = buffer_id,
1248 }; 1268 };
1249 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
1250} 1269}
1251 1270
1252template <class P> 1271template <class P>
@@ -1273,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1273 // Resolve buffer 1292 // Resolve buffer
1274 Binding& binding = channel_state->compute_storage_buffers[index]; 1293 Binding& binding = channel_state->compute_storage_buffers[index];
1275 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1294 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1276 // Mark as written if needed
1277 if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
1278 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1279 }
1280 }); 1295 });
1281} 1296}
1282 1297
@@ -1285,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
1285 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { 1300 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1286 Binding& binding = channel_state->compute_texture_buffers[index]; 1301 Binding& binding = channel_state->compute_texture_buffers[index];
1287 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1302 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1288 // Mark as written if needed
1289 if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
1290 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1291 }
1292 }); 1303 });
1293} 1304}
1294 1305
1295template <class P> 1306template <class P>
1296void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { 1307void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
1297 if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
1298 SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
1299 }
1300 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); 1308 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
1301 1309
1302 const IntervalType base_interval{cpu_addr, cpu_addr + size}; 1310 const IntervalType base_interval{cpu_addr, cpu_addr + size};
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.cpp b/src/video_core/engines/draw_manager.cpp
index f34090791..d77ff455b 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
48 SetInlineIndexBuffer(regs.inline_index_4x8.index3); 48 SetInlineIndexBuffer(regs.inline_index_4x8.index3);
49 break; 49 break;
50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first): 50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
51 DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
52 regs.vertex_array_instance_first.start.Value(),
53 regs.vertex_array_instance_first.count.Value(), false);
54 break;
51 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { 55 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
52 LOG_WARNING(HW_GPU, "(STUBBED) called"); 56 DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
57 regs.vertex_array_instance_subsequent.start.Value(),
58 regs.vertex_array_instance_subsequent.count.Value(), true);
53 break; 59 break;
54 } 60 }
55 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { 61 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
84 ProcessDraw(false, num_instances); 90 ProcessDraw(false, num_instances);
85} 91}
86 92
93void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
94 bool subsequent) {
95 draw_state.topology = topology;
96 draw_state.vertex_buffer.first = vertex_first;
97 draw_state.vertex_buffer.count = vertex_count;
98
99 if (!subsequent) {
100 draw_state.instance_count = 1;
101 }
102
103 draw_state.base_instance = draw_state.instance_count - 1;
104 draw_state.draw_mode = DrawMode::Instance;
105 draw_state.instance_count++;
106 ProcessDraw(false, 1);
107}
108
87void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, 109void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
88 u32 base_index, u32 base_instance, u32 num_instances) { 110 u32 base_index, u32 base_instance, u32 num_instances) {
89 const auto& regs{maxwell3d->regs}; 111 const auto& regs{maxwell3d->regs};
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 7c22c49f1..cfc8127fc 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;
@@ -65,6 +66,8 @@ public:
65 66
66 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, 67 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
67 u32 base_instance, u32 num_instances); 68 u32 base_instance, u32 num_instances);
69 void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
70 bool subsequent);
68 71
69 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, 72 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
70 u32 base_instance, u32 num_instances); 73 u32 base_instance, u32 num_instances);
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/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 8d7da50fc..dbcf508e5 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() {
137 break; 137 break;
138 } 138 }
139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { 139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
140#if defined(__unix__)
141 // Some linux decoding backends are reported to crash with this config method
142 // TODO(ameerj): Properly support this method
143 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
144 // skip zero-copy decoders, we don't currently support them
145 LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
146 av_hwdevice_get_type_name(type), config->methods);
147 continue;
148 }
149#endif
150 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); 140 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
151 av_codec_ctx->pix_fmt = config->pix_fmt; 141 av_codec_ctx->pix_fmt = config->pix_fmt;
152 return true; 142 return true;
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/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 9cafd2983..512eef575 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
1048} 1048}
1049 1049
1050bool Image::ScaleUp(bool ignore) { 1050bool Image::ScaleUp(bool ignore) {
1051 const auto& resolution = runtime->resolution;
1052 if (!resolution.active) {
1053 return false;
1054 }
1051 if (True(flags & ImageFlagBits::Rescaled)) { 1055 if (True(flags & ImageFlagBits::Rescaled)) {
1052 return false; 1056 return false;
1053 } 1057 }
@@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
1060 return false; 1064 return false;
1061 } 1065 }
1062 flags |= ImageFlagBits::Rescaled; 1066 flags |= ImageFlagBits::Rescaled;
1063 if (!runtime->resolution.active) {
1064 return false;
1065 }
1066 has_scaled = true; 1067 has_scaled = true;
1067 if (ignore) { 1068 if (ignore) {
1068 current_texture = upscaled_backup.handle; 1069 current_texture = upscaled_backup.handle;
@@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
1073} 1074}
1074 1075
1075bool Image::ScaleDown(bool ignore) { 1076bool Image::ScaleDown(bool ignore) {
1076 if (False(flags & ImageFlagBits::Rescaled)) { 1077 const auto& resolution = runtime->resolution;
1078 if (!resolution.active) {
1077 return false; 1079 return false;
1078 } 1080 }
1079 flags &= ~ImageFlagBits::Rescaled; 1081 if (False(flags & ImageFlagBits::Rescaled)) {
1080 if (!runtime->resolution.active) {
1081 return false; 1082 return false;
1082 } 1083 }
1084 flags &= ~ImageFlagBits::Rescaled;
1083 if (ignore) { 1085 if (ignore) {
1084 current_texture = texture.handle; 1086 current_texture = texture.handle;
1085 return true; 1087 return true;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
118 118
119 void InsertUploadMemoryBarrier(); 119 void InsertUploadMemoryBarrier();
120 120
121 void TransitionImageLayout(Image& image) {}
122
121 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; 123 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
122 124
123 bool HasNativeBgr() const noexcept { 125 bool HasNativeBgr() const noexcept {
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..93773a69f 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,20 +1514,31 @@ 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}
1495 1531
1496bool Image::ScaleUp(bool ignore) { 1532bool Image::ScaleUp(bool ignore) {
1533 const auto& resolution = runtime->resolution;
1534 if (!resolution.active) {
1535 return false;
1536 }
1497 if (True(flags & ImageFlagBits::Rescaled)) { 1537 if (True(flags & ImageFlagBits::Rescaled)) {
1498 return false; 1538 return false;
1499 } 1539 }
1500 ASSERT(info.type != ImageType::Linear); 1540 ASSERT(info.type != ImageType::Linear);
1501 flags |= ImageFlagBits::Rescaled; 1541 flags |= ImageFlagBits::Rescaled;
1502 const auto& resolution = runtime->resolution;
1503 if (!resolution.active) {
1504 return false;
1505 }
1506 has_scaled = true; 1542 has_scaled = true;
1507 if (!scaled_image) { 1543 if (!scaled_image) {
1508 const bool is_2d = info.type == ImageType::e2D; 1544 const bool is_2d = info.type == ImageType::e2D;
@@ -1531,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
1531} 1567}
1532 1568
1533bool Image::ScaleDown(bool ignore) { 1569bool Image::ScaleDown(bool ignore) {
1570 const auto& resolution = runtime->resolution;
1571 if (!resolution.active) {
1572 return false;
1573 }
1534 if (False(flags & ImageFlagBits::Rescaled)) { 1574 if (False(flags & ImageFlagBits::Rescaled)) {
1535 return false; 1575 return false;
1536 } 1576 }
1537 ASSERT(info.type != ImageType::Linear); 1577 ASSERT(info.type != ImageType::Linear);
1538 flags &= ~ImageFlagBits::Rescaled; 1578 flags &= ~ImageFlagBits::Rescaled;
1539 const auto& resolution = runtime->resolution;
1540 if (!resolution.active) {
1541 return false;
1542 }
1543 current_image = *original_image; 1579 current_image = *original_image;
1544 if (ignore) { 1580 if (ignore) {
1545 return true; 1581 return true;
@@ -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 }
@@ -1976,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
1976 ASSERT(false); 2013 ASSERT(false);
1977} 2014}
1978 2015
2016void TextureCacheRuntime::TransitionImageLayout(Image& image) {
2017 if (!image.ExchangeInitialization()) {
2018 VkImageMemoryBarrier barrier{
2019 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
2020 .pNext = nullptr,
2021 .srcAccessMask = VK_ACCESS_NONE,
2022 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
2023 .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
2024 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
2025 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2026 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2027 .image = image.Handle(),
2028 .subresourceRange{
2029 .aspectMask = image.AspectMask(),
2030 .baseMipLevel = 0,
2031 .levelCount = VK_REMAINING_MIP_LEVELS,
2032 .baseArrayLayer = 0,
2033 .layerCount = VK_REMAINING_ARRAY_LAYERS,
2034 },
2035 };
2036 scheduler.RequestOutsideRenderPassOperationContext();
2037 scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
2038 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2039 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
2040 });
2041 }
2042}
2043
1979} // namespace Vulkan 2044} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 565ce19a9..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
92 92
93 void InsertUploadMemoryBarrier() {} 93 void InsertUploadMemoryBarrier() {}
94 94
95 void TransitionImageLayout(Image& image);
96
95 bool HasBrokenTextureViewFormats() const noexcept { 97 bool HasBrokenTextureViewFormats() const noexcept {
96 // No known Vulkan driver has broken image views 98 // No known Vulkan driver has broken image views
97 return false; 99 return false;
@@ -117,6 +119,7 @@ public:
117 BlitImageHelper& blit_image_helper; 119 BlitImageHelper& blit_image_helper;
118 RenderPassCache& render_pass_cache; 120 RenderPassCache& render_pass_cache;
119 std::optional<ASTCDecoderPass> astc_decoder_pass; 121 std::optional<ASTCDecoderPass> astc_decoder_pass;
122 std::unique_ptr<MSAACopyPass> msaa_copy_pass;
120 const Settings::ResolutionScalingInfo& resolution; 123 const Settings::ResolutionScalingInfo& resolution;
121 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; 124 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
122 125
@@ -161,15 +164,13 @@ public:
161 return aspect_mask; 164 return aspect_mask;
162 } 165 }
163 166
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 167 /// Returns true when the image is already initialized and mark it as initialized
169 [[nodiscard]] bool ExchangeInitialization() noexcept { 168 [[nodiscard]] bool ExchangeInitialization() noexcept {
170 return std::exchange(initialized, true); 169 return std::exchange(initialized, true);
171 } 170 }
172 171
172 VkImageView StorageImageView(s32 level) noexcept;
173
173 bool IsRescaled() const noexcept; 174 bool IsRescaled() const noexcept;
174 175
175 bool ScaleUp(bool ignore = false); 176 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/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1bdb0def5..d575c57ca 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
1016 1016
1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { 1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); 1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
1019 runtime.TransitionImageLayout(image);
1019 return; 1020 return;
1020 } 1021 }
1021 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { 1022 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index ec4dd1f68..15596c925 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -1201,7 +1201,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1201 return std::nullopt; 1201 return std::nullopt;
1202 } 1202 }
1203 } else { 1203 } else {
1204 // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format 1204 // Format compatibility is not relaxed, ensure we are creating a view on a compatible format
1205 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { 1205 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
1206 return std::nullopt; 1206 return std::nullopt;
1207 } 1207 }
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..8dd1667f3 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"
@@ -65,12 +66,12 @@ struct Range {
65 switch (usage) { 66 switch (usage) {
66 case MemoryUsage::Upload: 67 case MemoryUsage::Upload:
67 case MemoryUsage::Stream: 68 case MemoryUsage::Stream:
68 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; 69 return VMA_ALLOCATION_CREATE_MAPPED_BIT |
70 VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
69 case MemoryUsage::Download: 71 case MemoryUsage::Download:
70 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; 72 return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
71 case MemoryUsage::DeviceLocal: 73 case MemoryUsage::DeviceLocal:
72 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | 74 return {};
73 VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
74 } 75 }
75 return {}; 76 return {};
76} 77}
@@ -212,7 +213,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
212 : device{device_}, allocator{device.GetAllocator()}, 213 : device{device_}, allocator{device.GetAllocator()},
213 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, 214 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
214 buffer_image_granularity{ 215 buffer_image_granularity{
215 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} 216 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
217 // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
218 // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
219 // the heap running out of memory. With RenderDoc attached and only a small host/device region,
220 // only allow the stream buffer in this memory heap.
221 if (device.HasDebuggingToolAttached()) {
222 using namespace Common::Literals;
223 ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
224 if (heap.size <= 256_MiB) {
225 valid_memory_types &= ~(1u << index);
226 }
227 });
228 }
229}
216 230
217MemoryAllocator::~MemoryAllocator() = default; 231MemoryAllocator::~MemoryAllocator() = default;
218 232
@@ -239,12 +253,11 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
239 253
240vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { 254vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
241 const VmaAllocationCreateInfo alloc_ci = { 255 const VmaAllocationCreateInfo alloc_ci = {
242 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | 256 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
243 MemoryUsageVmaFlags(usage),
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..34208ed74 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
@@ -382,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
382 discord_impl.cpp 384 discord_impl.cpp
383 discord_impl.h 385 discord_impl.h
384 ) 386 )
385 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) 387 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
386 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) 388 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
387endif() 389endif()
388 390
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index d15559518..ca0e14fad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -23,6 +23,7 @@
23#include "yuzu/configuration/configure_vibration.h" 23#include "yuzu/configuration/configure_vibration.h"
24#include "yuzu/configuration/input_profiles.h" 24#include "yuzu/configuration/input_profiles.h"
25#include "yuzu/main.h" 25#include "yuzu/main.h"
26#include "yuzu/util/controller_navigation.h"
26 27
27namespace { 28namespace {
28 29
@@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
132 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 133 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
133 }; 134 };
134 135
136 ui->labelError->setVisible(false);
137
135 // Setup/load everything prior to setting up connections. 138 // Setup/load everything prior to setting up connections.
136 // This avoids unintentionally changing the states of elements while loading them in. 139 // This avoids unintentionally changing the states of elements while loading them in.
137 SetSupportedControllers(); 140 SetSupportedControllers();
@@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
143 146
144 LoadConfiguration(); 147 LoadConfiguration();
145 148
149 controller_navigation = new ControllerNavigation(system.HIDCore(), this);
150
146 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { 151 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
147 SetExplainText(i); 152 SetExplainText(i);
148 UpdateControllerIcon(i); 153 UpdateControllerIcon(i);
@@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
151 156
152 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { 157 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
153 if (checked) { 158 if (checked) {
159 // Hide eventual error message about number of controllers
160 ui->labelError->setVisible(false);
154 for (std::size_t index = 0; index <= i; ++index) { 161 for (std::size_t index = 0; index <= i; ++index) {
155 connected_controller_checkboxes[index]->setChecked(checked); 162 connected_controller_checkboxes[index]->setChecked(checked);
156 } 163 }
@@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
199 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 206 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
200 &QtControllerSelectorDialog::ApplyConfiguration); 207 &QtControllerSelectorDialog::ApplyConfiguration);
201 208
209 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
210 [this](Qt::Key key) {
211 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
212 QCoreApplication::postEvent(this, event);
213 });
214
202 // Enhancement: Check if the parameters have already been met before disconnecting controllers. 215 // Enhancement: Check if the parameters have already been met before disconnecting controllers.
203 // If all the parameters are met AND only allows a single player, 216 // If all the parameters are met AND only allows a single player,
204 // stop the constructor here as we do not need to continue. 217 // stop the constructor here as we do not need to continue.
@@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
217} 230}
218 231
219QtControllerSelectorDialog::~QtControllerSelectorDialog() { 232QtControllerSelectorDialog::~QtControllerSelectorDialog() {
233 controller_navigation->UnloadController();
220 system.HIDCore().DisableAllControllerConfiguration(); 234 system.HIDCore().DisableAllControllerConfiguration();
221} 235}
222 236
@@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
291 dialog.exec(); 305 dialog.exec();
292} 306}
293 307
308void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
309 const auto num_connected_players = static_cast<int>(
310 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
311 [](const QGroupBox* player) { return player->isChecked(); }));
312
313 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
314 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
315
316 if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
317 // Display error message when trying to validate using "Enter" and "OK" button is disabled
318 ui->labelError->setVisible(true);
319 return;
320 } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
321 // Remove a player if possible
322 connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
323 return;
324 } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
325 // Add a player, if possible
326 ui->labelError->setVisible(false);
327 connected_controller_checkboxes[num_connected_players]->setChecked(true);
328 return;
329 }
330 QDialog::keyPressEvent(evt);
331}
332
294bool QtControllerSelectorDialog::CheckIfParametersMet() { 333bool QtControllerSelectorDialog::CheckIfParametersMet() {
295 // Here, we check and validate the current configuration against all applicable parameters. 334 // Here, we check and validate the current configuration against all applicable parameters.
296 const auto num_connected_players = static_cast<int>( 335 const auto num_connected_players = static_cast<int>(
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..7f0673d06 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -34,6 +34,8 @@ class HIDCore;
34enum class NpadStyleIndex : u8; 34enum class NpadStyleIndex : u8;
35} // namespace Core::HID 35} // namespace Core::HID
36 36
37class ControllerNavigation;
38
37class QtControllerSelectorDialog final : public QDialog { 39class QtControllerSelectorDialog final : public QDialog {
38 Q_OBJECT 40 Q_OBJECT
39 41
@@ -46,6 +48,8 @@ public:
46 48
47 int exec() override; 49 int exec() override;
48 50
51 void keyPressEvent(QKeyEvent* evt) override;
52
49private: 53private:
50 // Applies the current configuration. 54 // Applies the current configuration.
51 void ApplyConfiguration(); 55 void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
110 114
111 Core::System& system; 115 Core::System& system;
112 116
117 ControllerNavigation* controller_navigation = nullptr;
118
113 // This is true if and only if all parameters are met. Otherwise, this is false. 119 // This is true if and only if all parameters are met. Otherwise, this is false.
114 // This determines whether the "OK" button can be clicked to exit the applet. 120 // This determines whether the "OK" button can be clicked to exit the applet.
115 bool parameters_met{false}; 121 bool parameters_met{false};
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui
index 729e921ee..6f7cb3c13 100644
--- a/src/yuzu/applets/qt_controller.ui
+++ b/src/yuzu/applets/qt_controller.ui
@@ -2624,13 +2624,53 @@
2624 </spacer> 2624 </spacer>
2625 </item> 2625 </item>
2626 <item alignment="Qt::AlignBottom"> 2626 <item alignment="Qt::AlignBottom">
2627 <widget class="QDialogButtonBox" name="buttonBox"> 2627 <widget class="QWidget" name="closeButtons" native="true">
2628 <property name="enabled"> 2628 <layout class="QVBoxLayout" name="verticalLayout_46">
2629 <bool>true</bool> 2629 <property name="spacing">
2630 </property> 2630 <number>7</number>
2631 <property name="standardButtons"> 2631 </property>
2632 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> 2632 <property name="leftMargin">
2633 </property> 2633 <number>0</number>
2634 </property>
2635 <property name="topMargin">
2636 <number>0</number>
2637 </property>
2638 <property name="rightMargin">
2639 <number>0</number>
2640 </property>
2641 <property name="bottomMargin">
2642 <number>0</number>
2643 </property>
2644 <item>
2645 <widget class="QLabel" name="labelError">
2646 <property name="enabled">
2647 <bool>true</bool>
2648 </property>
2649 <property name="styleSheet">
2650 <string notr="true">QLabel { color : red; }</string>
2651 </property>
2652 <property name="text">
2653 <string>Not enough controllers</string>
2654 </property>
2655 <property name="alignment">
2656 <set>Qt::AlignCenter</set>
2657 </property>
2658 <property name="margin">
2659 <number>0</number>
2660 </property>
2661 </widget>
2662 </item>
2663 <item>
2664 <widget class="QDialogButtonBox" name="buttonBox">
2665 <property name="enabled">
2666 <bool>true</bool>
2667 </property>
2668 <property name="standardButtons">
2669 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
2670 </property>
2671 </widget>
2672 </item>
2673 </layout>
2634 </widget> 2674 </widget>
2635 </item> 2675 </item>
2636 </layout> 2676 </layout>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 1de093447..d5157c502 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, 128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, 129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, 130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, 131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, 132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
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_input.cpp b/src/yuzu/configuration/configure_input.cpp
index e8f9ebfd8..5a48e388b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
115 for (std::size_t i = 0; i < player_tabs.size(); ++i) { 115 for (std::size_t i = 0; i < player_tabs.size(); ++i) {
116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); 116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
117 player_tabs[i]->layout()->addWidget(player_controllers[i]); 117 player_tabs[i]->layout()->addWidget(player_controllers[i]);
118 connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { 118 connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
119 // Ensures that the controllers are always connected in sequential order 119 // Ensures that the controllers are always connected in sequential order
120 if (is_connected) { 120 this->propagateMouseClickOnPlayers(i, checked, true);
121 for (std::size_t index = 0; index <= i; ++index) {
122 player_connected[index]->setChecked(is_connected);
123 }
124 } else {
125 for (std::size_t index = i; index < player_tabs.size(); ++index) {
126 player_connected[index]->setChecked(is_connected);
127 }
128 }
129 }); 121 });
130 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, 122 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
131 &ConfigureInput::UpdateAllInputDevices); 123 &ConfigureInput::UpdateAllInputDevices);
@@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
183 LoadConfiguration(); 175 LoadConfiguration();
184} 176}
185 177
178void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
179 // Origin has already been toggled
180 if (!origin) {
181 player_connected[player_index]->setChecked(checked);
182 }
183
184 if (checked) {
185 // Check all previous buttons when checked
186 if (player_index > 0) {
187 propagateMouseClickOnPlayers(player_index - 1, checked, false);
188 }
189 } else {
190 // Unchecked all following buttons when unchecked
191 if (player_index < player_tabs.size() - 1) {
192 // Reconnect current player if it was the last one checked
193 // (player number was reduced by more than one)
194 if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
195 player_connected[player_index]->setCheckState(Qt::Checked);
196 }
197 propagateMouseClickOnPlayers(player_index + 1, checked, false);
198 }
199 }
200}
201
186QList<QWidget*> ConfigureInput::GetSubTabs() const { 202QList<QWidget*> ConfigureInput::GetSubTabs() const {
187 return { 203 return {
188 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, 204 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..abb7f7089 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,7 @@ private:
56 void UpdateDockedState(bool is_handheld); 56 void UpdateDockedState(bool is_handheld);
57 void UpdateAllInputDevices(); 57 void UpdateAllInputDevices();
58 void UpdateAllInputProfiles(std::size_t player_index); 58 void UpdateAllInputProfiles(std::size_t player_index);
59 void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
59 60
60 /// Load configuration settings. 61 /// Load configuration settings.
61 void LoadConfiguration(); 62 void LoadConfiguration();
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..3fe448f27 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", "");
@@ -156,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
156 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); 157 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
157 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); 158 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
158 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); 159 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
160 INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
159 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); 161 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
160 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); 162 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
161 163
@@ -382,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
382 translations->insert( 384 translations->insert(
383 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), 385 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
384 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); 386 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
387 translations->insert(
388 {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
389 {
390 PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
391 PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
392 PAIR(ConfirmStop, Ask_Never, "Never ask"),
393 }});
385 394
386#undef PAIR 395#undef PAIR
387#undef CTX_PAIR 396#undef CTX_PAIR
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..2bb1a0239 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,15 +824,17 @@ 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);
828
829 // Before deleting rows, cancel the worker so that it is not using them
830 emit ShouldCancelWorker();
820 831
821 // Delete any rows that might already exist if we're repopulating 832 // Delete any rows that might already exist if we're repopulating
822 item_model->removeRows(0, item_model->rowCount()); 833 item_model->removeRows(0, item_model->rowCount());
823 search_field->clear(); 834 search_field->clear();
824 835
825 emit ShouldCancelWorker();
826
827 GameListWorker* worker = 836 GameListWorker* worker =
828 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 837 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
829 838
830 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 839 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
831 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, 840 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..077ced12b 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}
@@ -288,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
288void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, 293void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
289 GameListDir* parent_dir) { 294 GameListDir* parent_dir) {
290 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { 295 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
291 if (stop_processing) { 296 if (stop_requested) {
292 // Breaks the callback loop. 297 // Breaks the callback loop.
293 return false; 298 return false;
294 } 299 }
@@ -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) {
@@ -392,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
392} 399}
393 400
394void GameListWorker::run() { 401void GameListWorker::run() {
395 stop_processing = false;
396 provider->ClearAllEntries(); 402 provider->ClearAllEntries();
397 403
398 for (UISettings::GameDir& game_dir : game_dirs) { 404 for (UISettings::GameDir& game_dir : game_dirs) {
@@ -420,9 +426,11 @@ void GameListWorker::run() {
420 } 426 }
421 427
422 emit Finished(watch_list); 428 emit Finished(watch_list);
429 processing_completed.Set();
423} 430}
424 431
425void GameListWorker::Cancel() { 432void GameListWorker::Cancel() {
426 this->disconnect(); 433 this->disconnect();
427 stop_processing = true; 434 stop_requested.store(true);
435 processing_completed.Wait();
428} 436}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..54dc05e30 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -12,7 +12,9 @@
12#include <QRunnable> 12#include <QRunnable>
13#include <QString> 13#include <QString>
14 14
15#include "common/thread.h"
15#include "yuzu/compatibility_list.h" 16#include "yuzu/compatibility_list.h"
17#include "yuzu/play_time_manager.h"
16 18
17namespace Core { 19namespace Core {
18class System; 20class System;
@@ -36,7 +38,9 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 38 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 39 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 40 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 41 const CompatibilityList& compatibility_list_,
42 const PlayTime::PlayTimeManager& play_time_manager_,
43 Core::System& system_);
40 ~GameListWorker() override; 44 ~GameListWorker() override;
41 45
42 /// Starts the processing of directory tree information. 46 /// Starts the processing of directory tree information.
@@ -76,9 +80,12 @@ private:
76 FileSys::ManualContentProvider* provider; 80 FileSys::ManualContentProvider* provider;
77 QVector<UISettings::GameDir>& game_dirs; 81 QVector<UISettings::GameDir>& game_dirs;
78 const CompatibilityList& compatibility_list; 82 const CompatibilityList& compatibility_list;
83 const PlayTime::PlayTimeManager& play_time_manager;
79 84
80 QStringList watch_list; 85 QStringList watch_list;
81 std::atomic_bool stop_processing; 86
87 Common::Event processing_completed;
88 std::atomic_bool stop_requested = false;
82 89
83 Core::System& system; 90 Core::System& system;
84}; 91};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index d32aa9615..1431cf2fe 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"
@@ -65,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
65#define QT_NO_OPENGL 67#define QT_NO_OPENGL
66#include <QClipboard> 68#include <QClipboard>
67#include <QDesktopServices> 69#include <QDesktopServices>
70#include <QDir>
68#include <QFile> 71#include <QFile>
69#include <QFileDialog> 72#include <QFileDialog>
70#include <QInputDialog> 73#include <QInputDialog>
@@ -74,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
74#include <QPushButton> 77#include <QPushButton>
75#include <QScreen> 78#include <QScreen>
76#include <QShortcut> 79#include <QShortcut>
80#include <QStandardPaths>
77#include <QStatusBar> 81#include <QStatusBar>
78#include <QString> 82#include <QString>
79#include <QSysInfo> 83#include <QSysInfo>
@@ -96,6 +100,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
96#include "common/scm_rev.h" 100#include "common/scm_rev.h"
97#include "common/scope_exit.h" 101#include "common/scope_exit.h"
98#ifdef _WIN32 102#ifdef _WIN32
103#include <shlobj.h>
99#include "common/windows/timer_resolution.h" 104#include "common/windows/timer_resolution.h"
100#endif 105#endif
101#ifdef ARCHITECTURE_x86_64 106#ifdef ARCHITECTURE_x86_64
@@ -148,6 +153,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
148#include "yuzu/install_dialog.h" 153#include "yuzu/install_dialog.h"
149#include "yuzu/loading_screen.h" 154#include "yuzu/loading_screen.h"
150#include "yuzu/main.h" 155#include "yuzu/main.h"
156#include "yuzu/play_time_manager.h"
151#include "yuzu/startup_checks.h" 157#include "yuzu/startup_checks.h"
152#include "yuzu/uisettings.h" 158#include "yuzu/uisettings.h"
153#include "yuzu/util/clickable_label.h" 159#include "yuzu/util/clickable_label.h"
@@ -205,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
205 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " 211 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
206 "data is collected</a> to help improve yuzu. " 212 "data is collected</a> to help improve yuzu. "
207 "<br/><br/>Would you like to share your usage data with us?"); 213 "<br/><br/>Would you like to share your usage data with us?");
208 if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { 214 if (!question(this, tr("Telemetry"), telemetry_message)) {
209 Settings::values.enable_telemetry = false; 215 Settings::values.enable_telemetry = false;
210 system->ApplySettings(); 216 system->ApplySettings();
211 } 217 }
@@ -336,6 +342,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
336 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 342 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
337 discord_rpc->Update(); 343 discord_rpc->Update();
338 344
345 play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
346
339 system->GetRoomNetwork().Init(); 347 system->GetRoomNetwork().Init();
340 348
341 RegisterMetaTypes(); 349 RegisterMetaTypes();
@@ -984,7 +992,7 @@ void GMainWindow::InitializeWidgets() {
984 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); 992 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
985 render_window->hide(); 993 render_window->hide();
986 994
987 game_list = new GameList(vfs, provider.get(), *system, this); 995 game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
988 ui->horizontalLayout->addWidget(game_list); 996 ui->horizontalLayout->addWidget(game_list);
989 997
990 game_list_placeholder = new GameListPlaceholder(this); 998 game_list_placeholder = new GameListPlaceholder(this);
@@ -1445,6 +1453,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
1445 Settings::values.audio_muted = false; 1453 Settings::values.audio_muted = false;
1446 auto_muted = false; 1454 auto_muted = false;
1447 } 1455 }
1456 UpdateVolumeUI();
1448 } 1457 }
1449} 1458}
1450 1459
@@ -1458,6 +1467,8 @@ void GMainWindow::ConnectWidgetEvents() {
1458 connect(game_list, &GameList::RemoveInstalledEntryRequested, this, 1467 connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
1459 &GMainWindow::OnGameListRemoveInstalledEntry); 1468 &GMainWindow::OnGameListRemoveInstalledEntry);
1460 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); 1469 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
1470 connect(game_list, &GameList::RemovePlayTimeRequested, this,
1471 &GMainWindow::OnGameListRemovePlayTimeData);
1461 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 1472 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
1462 connect(game_list, &GameList::VerifyIntegrityRequested, this, 1473 connect(game_list, &GameList::VerifyIntegrityRequested, this,
1463 &GMainWindow::OnGameListVerifyIntegrity); 1474 &GMainWindow::OnGameListVerifyIntegrity);
@@ -1551,6 +1562,16 @@ void GMainWindow::ConnectMenuEvents() {
1551 // Tools 1562 // Tools
1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1563 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1553 ReinitializeKeyBehavior::Warning)); 1564 ReinitializeKeyBehavior::Warning));
1565 connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
1566 connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
1567 [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
1568 connect_menu(ui->action_Load_Cabinet_Eraser,
1569 [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
1570 connect_menu(ui->action_Load_Cabinet_Restorer,
1571 [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
1572 connect_menu(ui->action_Load_Cabinet_Formatter,
1573 [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
1574 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1554 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1575 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1555 1576
1556 // TAS 1577 // TAS
@@ -1567,6 +1588,7 @@ void GMainWindow::ConnectMenuEvents() {
1567 1588
1568void GMainWindow::UpdateMenuState() { 1589void GMainWindow::UpdateMenuState() {
1569 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); 1590 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
1591 const bool is_firmware_available = CheckFirmwarePresence();
1570 1592
1571 const std::array running_actions{ 1593 const std::array running_actions{
1572 ui->action_Stop, 1594 ui->action_Stop,
@@ -1577,10 +1599,23 @@ void GMainWindow::UpdateMenuState() {
1577 ui->action_Pause, 1599 ui->action_Pause,
1578 }; 1600 };
1579 1601
1602 const std::array applet_actions{
1603 ui->action_Load_Album,
1604 ui->action_Load_Cabinet_Nickname_Owner,
1605 ui->action_Load_Cabinet_Eraser,
1606 ui->action_Load_Cabinet_Restorer,
1607 ui->action_Load_Cabinet_Formatter,
1608 ui->action_Load_Mii_Edit,
1609 };
1610
1580 for (QAction* action : running_actions) { 1611 for (QAction* action : running_actions) {
1581 action->setEnabled(emulation_running); 1612 action->setEnabled(emulation_running);
1582 } 1613 }
1583 1614
1615 for (QAction* action : applet_actions) {
1616 action->setEnabled(is_firmware_available && !emulation_running);
1617 }
1618
1584 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); 1619 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
1585 1620
1586 if (emulation_running && is_paused) { 1621 if (emulation_running && is_paused) {
@@ -2100,6 +2135,8 @@ void GMainWindow::OnEmulationStopped() {
2100 OnTasStateChanged(); 2135 OnTasStateChanged();
2101 render_window->FinalizeCamera(); 2136 render_window->FinalizeCamera();
2102 2137
2138 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
2139
2103 // Enable all controllers 2140 // Enable all controllers
2104 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); 2141 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
2105 2142
@@ -2383,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
2383 } 2420 }
2384 }(); 2421 }();
2385 2422
2386 if (QMessageBox::question(this, tr("Remove Entry"), entry_question, 2423 if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
2387 QMessageBox::Yes | QMessageBox::No, 2424 QMessageBox::No)) {
2388 QMessageBox::No) != QMessageBox::Yes) {
2389 return; 2425 return;
2390 } 2426 }
2391 2427
@@ -2484,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2484 } 2520 }
2485 }(); 2521 }();
2486 2522
2487 if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, 2523 if (!GMainWindow::question(this, tr("Remove File"), question,
2488 QMessageBox::No) != QMessageBox::Yes) { 2524 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
2489 return; 2525 return;
2490 } 2526 }
2491 2527
@@ -2508,6 +2544,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2508 } 2544 }
2509} 2545}
2510 2546
2547void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
2548 if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
2549 QMessageBox::Yes | QMessageBox::No,
2550 QMessageBox::No) != QMessageBox::Yes) {
2551 return;
2552 }
2553
2554 play_time_manager->ResetProgramPlayTime(program_id);
2555 game_list->PopulateAsync(UISettings::values.game_dirs);
2556}
2557
2511void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { 2558void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
2512 const auto target_file_name = [target] { 2559 const auto target_file_name = [target] {
2513 switch (target) { 2560 switch (target) {
@@ -2799,7 +2846,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2799 const QStringList args = QApplication::arguments(); 2846 const QStringList args = QApplication::arguments();
2800 std::filesystem::path yuzu_command = args[0].toStdString(); 2847 std::filesystem::path yuzu_command = args[0].toStdString();
2801 2848
2802#if defined(__linux__) || defined(__FreeBSD__)
2803 // If relative path, make it an absolute path 2849 // If relative path, make it an absolute path
2804 if (yuzu_command.c_str()[0] == '.') { 2850 if (yuzu_command.c_str()[0] == '.') {
2805 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2851 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2822,44 +2868,52 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2822 UISettings::values.shortcut_already_warned = true; 2868 UISettings::values.shortcut_already_warned = true;
2823 } 2869 }
2824#endif // __linux__ 2870#endif // __linux__
2825#endif // __linux__ || __FreeBSD__
2826 2871
2827 std::filesystem::path target_directory{}; 2872 std::filesystem::path target_directory{};
2828 // Determine target directory for shortcut
2829#if defined(__linux__) || defined(__FreeBSD__)
2830 const char* home = std::getenv("HOME");
2831 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2832 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2833 2873
2834 if (target == GameListShortcutTarget::Desktop) { 2874 switch (target) {
2835 target_directory = home_path / "Desktop"; 2875 case GameListShortcutTarget::Desktop: {
2836 if (!Common::FS::IsDir(target_directory)) { 2876 const QString desktop_path =
2837 QMessageBox::critical( 2877 QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2838 this, tr("Create Shortcut"), 2878 target_directory = desktop_path.toUtf8().toStdString();
2839 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2879 break;
2840 .arg(QString::fromStdString(target_directory)), 2880 }
2841 QMessageBox::StandardButton::Ok); 2881 case GameListShortcutTarget::Applications: {
2842 return; 2882 const QString applications_path =
2843 } 2883 QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
2844 } else if (target == GameListShortcutTarget::Applications) { 2884 if (applications_path.isEmpty()) {
2845 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2885 const char* home = std::getenv("HOME");
2846 "applications"; 2886 if (home != nullptr) {
2847 if (!Common::FS::CreateDirs(target_directory)) { 2887 target_directory = std::filesystem::path(home) / ".local/share/applications";
2848 QMessageBox::critical(this, tr("Create Shortcut"), 2888 }
2849 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2889 } else {
2850 "does not exist and cannot be created.") 2890 target_directory = applications_path.toUtf8().toStdString();
2851 .arg(QString::fromStdString(target_directory)),
2852 QMessageBox::StandardButton::Ok);
2853 return;
2854 } 2891 }
2892 break;
2893 }
2894 default:
2895 return;
2896 }
2897
2898 const QDir dir(QString::fromStdString(target_directory.generic_string()));
2899 if (!dir.exists()) {
2900 QMessageBox::critical(this, tr("Create Shortcut"),
2901 tr("Cannot create shortcut. Path \"%1\" does not exist.")
2902 .arg(QString::fromStdString(target_directory.generic_string())),
2903 QMessageBox::StandardButton::Ok);
2904 return;
2855 } 2905 }
2856#endif
2857 2906
2858 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2907 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2859 // Determine full paths for icon and shortcut 2908 // Determine full paths for icon and shortcut
2860#if defined(__linux__) || defined(__FreeBSD__) 2909#if defined(__linux__) || defined(__FreeBSD__)
2910 const char* home = std::getenv("HOME");
2911 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2912 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2913
2861 std::filesystem::path system_icons_path = 2914 std::filesystem::path system_icons_path =
2862 (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / 2915 (xdg_data_home == nullptr ? home_path / ".local/share/"
2916 : std::filesystem::path(xdg_data_home)) /
2863 "icons/hicolor/256x256"; 2917 "icons/hicolor/256x256";
2864 if (!Common::FS::CreateDirs(system_icons_path)) { 2918 if (!Common::FS::CreateDirs(system_icons_path)) {
2865 QMessageBox::critical( 2919 QMessageBox::critical(
@@ -2875,9 +2929,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2875 const std::filesystem::path shortcut_path = 2929 const std::filesystem::path shortcut_path =
2876 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2930 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2877 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2931 : fmt::format("yuzu-{:016X}.desktop", program_id));
2932#elif defined(WIN32)
2933 std::filesystem::path icons_path =
2934 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2935 std::filesystem::path icon_path =
2936 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2937 : fmt::format("yuzu-{:016X}.ico", program_id)));
2878#else 2938#else
2879 const std::filesystem::path icon_path{}; 2939 std::string icon_extension;
2880 const std::filesystem::path shortcut_path{};
2881#endif 2940#endif
2882 2941
2883 // Get title from game file 2942 // Get title from game file
@@ -2902,29 +2961,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2902 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2961 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2903 } 2962 }
2904 2963
2905 QImage icon_jpeg = 2964 QImage icon_data =
2906 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2965 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2907#if defined(__linux__) || defined(__FreeBSD__) 2966#if defined(__linux__) || defined(__FreeBSD__)
2908 // Convert and write the icon as a PNG 2967 // Convert and write the icon as a PNG
2909 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2968 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2910 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2969 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2911 } else { 2970 } else {
2912 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2971 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2913 } 2972 }
2973#elif defined(WIN32)
2974 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2975 LOG_ERROR(Frontend, "Could not write icon to file");
2976 return;
2977 }
2914#endif // __linux__ 2978#endif // __linux__
2915 2979
2916#if defined(__linux__) || defined(__FreeBSD__) 2980#ifdef _WIN32
2981 // Replace characters that are illegal in Windows filenames by a dash
2982 const std::string illegal_chars = "<>:\"/\\|?*";
2983 for (char c : illegal_chars) {
2984 std::replace(title.begin(), title.end(), c, '_');
2985 }
2986 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2987#endif
2988
2917 const std::string comment = 2989 const std::string comment =
2918 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2990 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2919 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2991 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2920 const std::string categories = "Game;Emulator;Qt;"; 2992 const std::string categories = "Game;Emulator;Qt;";
2921 const std::string keywords = "Switch;Nintendo;"; 2993 const std::string keywords = "Switch;Nintendo;";
2922#else 2994
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(), 2995 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2929 yuzu_command.string(), arguments, categories, keywords)) { 2996 yuzu_command.string(), arguments, categories, keywords)) {
2930 QMessageBox::critical(this, tr("Create Shortcut"), 2997 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3110,10 +3177,9 @@ void GMainWindow::OnMenuInstallToNAND() {
3110 QFuture<InstallResult> future; 3177 QFuture<InstallResult> future;
3111 InstallResult result; 3178 InstallResult result;
3112 3179
3113 if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || 3180 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3114 file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3115 3181
3116 future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); 3182 future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
3117 3183
3118 while (!future.isFinished()) { 3184 while (!future.isFinished()) {
3119 QCoreApplication::processEvents(); 3185 QCoreApplication::processEvents();
@@ -3172,7 +3238,7 @@ void GMainWindow::OnMenuInstallToNAND() {
3172 ui->action_Install_File_NAND->setEnabled(true); 3238 ui->action_Install_File_NAND->setEnabled(true);
3173} 3239}
3174 3240
3175InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { 3241InstallResult GMainWindow::InstallNSP(const QString& filename) {
3176 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, 3242 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3177 const FileSys::VirtualFile& dest, std::size_t block_size) { 3243 const FileSys::VirtualFile& dest, std::size_t block_size) {
3178 if (src == nullptr || dest == nullptr) { 3244 if (src == nullptr || dest == nullptr) {
@@ -3206,9 +3272,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3206 return InstallResult::Failure; 3272 return InstallResult::Failure;
3207 } 3273 }
3208 } else { 3274 } else {
3209 const auto xci = std::make_shared<FileSys::XCI>( 3275 return InstallResult::Failure;
3210 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3211 nsp = xci->GetSecurePartitionNSP();
3212 } 3276 }
3213 3277
3214 if (nsp->GetStatus() != Loader::ResultStatus::Success) { 3278 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -3334,6 +3398,9 @@ void GMainWindow::OnStartGame() {
3334 UpdateMenuState(); 3398 UpdateMenuState();
3335 OnTasStateChanged(); 3399 OnTasStateChanged();
3336 3400
3401 play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
3402 play_time_manager->Start();
3403
3337 discord_rpc->Update(); 3404 discord_rpc->Update();
3338} 3405}
3339 3406
@@ -3341,14 +3408,18 @@ void GMainWindow::OnRestartGame() {
3341 if (!system->IsPoweredOn()) { 3408 if (!system->IsPoweredOn()) {
3342 return; 3409 return;
3343 } 3410 }
3344 // Make a copy since ShutdownGame edits game_path 3411
3345 const auto current_game = QString(current_game_path); 3412 if (ConfirmShutdownGame()) {
3346 ShutdownGame(); 3413 // Make a copy since ShutdownGame edits game_path
3347 BootGame(current_game); 3414 const auto current_game = QString(current_game_path);
3415 ShutdownGame();
3416 BootGame(current_game);
3417 }
3348} 3418}
3349 3419
3350void GMainWindow::OnPauseGame() { 3420void GMainWindow::OnPauseGame() {
3351 emu_thread->SetRunning(false); 3421 emu_thread->SetRunning(false);
3422 play_time_manager->Stop();
3352 UpdateMenuState(); 3423 UpdateMenuState();
3353 AllowOSSleep(); 3424 AllowOSSleep();
3354} 3425}
@@ -3365,15 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
3365} 3436}
3366 3437
3367void GMainWindow::OnStopGame() { 3438void GMainWindow::OnStopGame() {
3368 if (system->GetExitLocked() && !ConfirmForceLockedExit()) { 3439 if (ConfirmShutdownGame()) {
3369 return; 3440 play_time_manager->Stop();
3441 // Update game list to show new play time
3442 game_list->PopulateAsync(UISettings::values.game_dirs);
3443 if (OnShutdownBegin()) {
3444 OnShutdownBeginDialog();
3445 } else {
3446 OnEmulationStopped();
3447 }
3370 } 3448 }
3449}
3371 3450
3372 if (OnShutdownBegin()) { 3451bool GMainWindow::ConfirmShutdownGame() {
3373 OnShutdownBeginDialog(); 3452 if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
3453 if (system->GetExitLocked()) {
3454 if (!ConfirmForceLockedExit()) {
3455 return false;
3456 }
3457 } else {
3458 if (!ConfirmChangeGame()) {
3459 return false;
3460 }
3461 }
3374 } else { 3462 } else {
3375 OnEmulationStopped(); 3463 if (UISettings::values.confirm_before_stopping.GetValue() ==
3464 ConfirmStop::Ask_Based_On_Game &&
3465 system->GetExitLocked()) {
3466 if (!ConfirmForceLockedExit()) {
3467 return false;
3468 }
3469 }
3376 } 3470 }
3471 return true;
3377} 3472}
3378 3473
3379void GMainWindow::OnLoadComplete() { 3474void GMainWindow::OnLoadComplete() {
@@ -3753,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
3753 const bool is_recording = input_subsystem->GetTas()->Record(); 3848 const bool is_recording = input_subsystem->GetTas()->Record();
3754 if (!is_recording) { 3849 if (!is_recording) {
3755 is_tas_recording_dialog_active = true; 3850 is_tas_recording_dialog_active = true;
3756 ControllerNavigation* controller_navigation = 3851
3757 new ControllerNavigation(system->HIDCore(), this); 3852 bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
3758 // Use QMessageBox instead of question so we can link controller navigation 3853 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
3759 QMessageBox* box_dialog = new QMessageBox(); 3854
3760 box_dialog->setWindowTitle(tr("TAS Recording")); 3855 input_subsystem->GetTas()->SaveRecording(answer);
3761 box_dialog->setText(tr("Overwrite file of player 1?"));
3762 box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
3763 box_dialog->setDefaultButton(QMessageBox::Yes);
3764 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
3765 [box_dialog](Qt::Key key) {
3766 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
3767 QCoreApplication::postEvent(box_dialog, event);
3768 });
3769 int res = box_dialog->exec();
3770 controller_navigation->UnloadController();
3771 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
3772 is_tas_recording_dialog_active = false; 3856 is_tas_recording_dialog_active = false;
3773 } 3857 }
3774 OnTasStateChanged(); 3858 OnTasStateChanged();
@@ -3942,6 +4026,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3942 shortcut_stream.close(); 4026 shortcut_stream.close();
3943 4027
3944 return true; 4028 return true;
4029#elif defined(WIN32)
4030 IShellLinkW* shell_link;
4031 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4032 (void**)&shell_link);
4033 if (FAILED(hres)) {
4034 return false;
4035 }
4036 shell_link->SetPath(
4037 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4038 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4039 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4040 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4041
4042 IPersistFile* persist_file;
4043 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4044 if (FAILED(hres)) {
4045 return false;
4046 }
4047
4048 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4049 if (FAILED(hres)) {
4050 return false;
4051 }
4052
4053 persist_file->Release();
4054 shell_link->Release();
4055
4056 return true;
3945#endif 4057#endif
3946 return false; 4058 return false;
3947} 4059}
@@ -3981,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
3981 LoadAmiibo(filename); 4093 LoadAmiibo(filename);
3982} 4094}
3983 4095
4096bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
4097 QMessageBox::StandardButtons buttons,
4098 QMessageBox::StandardButton defaultButton) {
4099
4100 QMessageBox* box_dialog = new QMessageBox(parent);
4101 box_dialog->setWindowTitle(title);
4102 box_dialog->setText(text);
4103 box_dialog->setStandardButtons(buttons);
4104 box_dialog->setDefaultButton(defaultButton);
4105
4106 ControllerNavigation* controller_navigation =
4107 new ControllerNavigation(system->HIDCore(), box_dialog);
4108 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
4109 [box_dialog](Qt::Key key) {
4110 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
4111 QCoreApplication::postEvent(box_dialog, event);
4112 });
4113 int res = box_dialog->exec();
4114
4115 controller_navigation->UnloadController();
4116 return res == QMessageBox::Yes;
4117}
4118
3984void GMainWindow::LoadAmiibo(const QString& filename) { 4119void GMainWindow::LoadAmiibo(const QString& filename) {
3985 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); 4120 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
3986 const QString title = tr("Error loading Amiibo data"); 4121 const QString title = tr("Error loading Amiibo data");
@@ -4134,6 +4269,76 @@ void GMainWindow::OnToggleStatusBar() {
4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4269 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4135} 4270}
4136 4271
4272void GMainWindow::OnAlbum() {
4273 constexpr u64 AlbumId = 0x010000000000100Dull;
4274 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4275 if (!bis_system) {
4276 QMessageBox::warning(this, tr("No firmware available"),
4277 tr("Please install the firmware to use the Album applet."));
4278 return;
4279 }
4280
4281 auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
4282 if (!album_nca) {
4283 QMessageBox::warning(this, tr("Album Applet"),
4284 tr("Album applet is not available. Please reinstall firmware."));
4285 return;
4286 }
4287
4288 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
4289
4290 const auto filename = QString::fromStdString(album_nca->GetFullPath());
4291 UISettings::values.roms_path = QFileInfo(filename).path();
4292 BootGame(filename);
4293}
4294
4295void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
4296 constexpr u64 CabinetId = 0x0100000000001002ull;
4297 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4298 if (!bis_system) {
4299 QMessageBox::warning(this, tr("No firmware available"),
4300 tr("Please install the firmware to use the Cabinet applet."));
4301 return;
4302 }
4303
4304 auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
4305 if (!cabinet_nca) {
4306 QMessageBox::warning(this, tr("Cabinet Applet"),
4307 tr("Cabinet applet is not available. Please reinstall firmware."));
4308 return;
4309 }
4310
4311 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
4312 system->GetAppletManager().SetCabinetMode(mode);
4313
4314 const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
4315 UISettings::values.roms_path = QFileInfo(filename).path();
4316 BootGame(filename);
4317}
4318
4319void GMainWindow::OnMiiEdit() {
4320 constexpr u64 MiiEditId = 0x0100000000001009ull;
4321 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4322 if (!bis_system) {
4323 QMessageBox::warning(this, tr("No firmware available"),
4324 tr("Please install the firmware to use the Mii editor."));
4325 return;
4326 }
4327
4328 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4329 if (!mii_applet_nca) {
4330 QMessageBox::warning(this, tr("Mii Edit Applet"),
4331 tr("Mii editor is not available. Please reinstall firmware."));
4332 return;
4333 }
4334
4335 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
4336
4337 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
4338 UISettings::values.roms_path = QFileInfo(filename).path();
4339 BootGame(filename);
4340}
4341
4137void GMainWindow::OnCaptureScreenshot() { 4342void GMainWindow::OnCaptureScreenshot() {
4138 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4343 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4139 return; 4344 return;
@@ -4540,6 +4745,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
4540 if (behavior == ReinitializeKeyBehavior::Warning) { 4745 if (behavior == ReinitializeKeyBehavior::Warning) {
4541 game_list->PopulateAsync(UISettings::values.game_dirs); 4746 game_list->PopulateAsync(UISettings::values.game_dirs);
4542 } 4747 }
4748
4749 UpdateMenuState();
4543} 4750}
4544 4751
4545bool GMainWindow::CheckSystemArchiveDecryption() { 4752bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4561,10 +4768,26 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4561 return mii_nca->GetRomFS().get() != nullptr; 4768 return mii_nca->GetRomFS().get() != nullptr;
4562} 4769}
4563 4770
4771bool GMainWindow::CheckFirmwarePresence() {
4772 constexpr u64 MiiEditId = 0x0100000000001009ull;
4773
4774 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4775 if (!bis_system) {
4776 return false;
4777 }
4778
4779 auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
4780 if (!mii_applet_nca) {
4781 return false;
4782 }
4783
4784 return true;
4785}
4786
4564bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4787bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4565 u64* selected_title_id, u8* selected_content_record_type) { 4788 u64* selected_title_id, u8* selected_content_record_type) {
4566 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; 4789 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
4567 boost::container::flat_map<u64, ContentInfo> available_title_ids; 4790 boost::container::flat_set<ContentInfo> available_title_ids;
4568 4791
4569 const auto RetrieveEntries = [&](FileSys::TitleType title_type, 4792 const auto RetrieveEntries = [&](FileSys::TitleType title_type,
4570 FileSys::ContentRecordType record_type) { 4793 FileSys::ContentRecordType record_type) {
@@ -4572,12 +4795,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4572 for (const auto& entry : entries) { 4795 for (const auto& entry : entries) {
4573 if (FileSys::GetBaseTitleID(entry.title_id) == program_id && 4796 if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
4574 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { 4797 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
4575 available_title_ids[entry.title_id] = {title_type, record_type}; 4798 available_title_ids.insert({entry.title_id, title_type, record_type});
4576 } 4799 }
4577 } 4800 }
4578 }; 4801 };
4579 4802
4580 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); 4803 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
4804 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
4805 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
4581 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 4806 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
4582 4807
4583 if (available_title_ids.empty()) { 4808 if (available_title_ids.empty()) {
@@ -4588,10 +4813,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4588 4813
4589 if (available_title_ids.size() > 1) { 4814 if (available_title_ids.size() > 1) {
4590 QStringList list; 4815 QStringList list;
4591 for (auto& [title_id, content_info] : available_title_ids) { 4816 for (auto& [title_id, title_type, record_type] : available_title_ids) {
4592 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); 4817 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
4593 if (content_info.first == FileSys::TitleType::Application) { 4818 if (record_type == FileSys::ContentRecordType::Program) {
4594 list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id)); 4819 list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
4820 } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
4821 list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
4822 } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
4823 list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
4595 } else { 4824 } else {
4596 list.push_back( 4825 list.push_back(
4597 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); 4826 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
@@ -4609,9 +4838,9 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4609 title_index = list.indexOf(res); 4838 title_index = list.indexOf(res);
4610 } 4839 }
4611 4840
4612 const auto selected_info = available_title_ids.nth(title_index); 4841 const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
4613 *selected_title_id = selected_info->first; 4842 *selected_title_id = title_id;
4614 *selected_content_record_type = static_cast<u8>(selected_info->second.second); 4843 *selected_content_record_type = static_cast<u8>(record_type);
4615 return true; 4844 return true;
4616} 4845}
4617 4846
@@ -4620,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
4620 return true; 4849 return true;
4621 } 4850 }
4622 const auto text = tr("Are you sure you want to close yuzu?"); 4851 const auto text = tr("Are you sure you want to close yuzu?");
4623 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4852 return question(this, tr("yuzu"), text);
4624 return answer != QMessageBox::No;
4625} 4853}
4626 4854
4627void GMainWindow::closeEvent(QCloseEvent* event) { 4855void GMainWindow::closeEvent(QCloseEvent* event) {
@@ -4714,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
4714 if (emu_thread == nullptr) 4942 if (emu_thread == nullptr)
4715 return true; 4943 return true;
4716 4944
4717 const auto answer = QMessageBox::question( 4945 // Use custom question to link controller navigation
4946 return question(
4718 this, tr("yuzu"), 4947 this, tr("yuzu"),
4719 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), 4948 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
4720 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 4949 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
4721 return answer != QMessageBox::No;
4722} 4950}
4723 4951
4724bool GMainWindow::ConfirmForceLockedExit() { 4952bool GMainWindow::ConfirmForceLockedExit() {
@@ -4728,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
4728 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" 4956 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
4729 "Would you like to bypass this and exit anyway?"); 4957 "Would you like to bypass this and exit anyway?");
4730 4958
4731 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4959 return question(this, tr("yuzu"), text);
4732 return answer != QMessageBox::No;
4733} 4960}
4734 4961
4735void GMainWindow::RequestGameExit() { 4962void GMainWindow::RequestGameExit() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index cf191f698..270a40c5f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -7,6 +7,7 @@
7#include <optional> 7#include <optional>
8 8
9#include <QMainWindow> 9#include <QMainWindow>
10#include <QMessageBox>
10#include <QTimer> 11#include <QTimer>
11#include <QTranslator> 12#include <QTranslator>
12 13
@@ -15,6 +16,7 @@
15#include "input_common/drivers/tas_input.h" 16#include "input_common/drivers/tas_input.h"
16#include "yuzu/compatibility_list.h" 17#include "yuzu/compatibility_list.h"
17#include "yuzu/hotkeys.h" 18#include "yuzu/hotkeys.h"
19#include "yuzu/util/controller_navigation.h"
18 20
19#ifdef __unix__ 21#ifdef __unix__
20#include <QVariant> 22#include <QVariant>
@@ -81,6 +83,10 @@ namespace DiscordRPC {
81class DiscordInterface; 83class DiscordInterface;
82} 84}
83 85
86namespace PlayTime {
87class PlayTimeManager;
88}
89
84namespace FileSys { 90namespace FileSys {
85class ContentProvider; 91class ContentProvider;
86class ManualContentProvider; 92class ManualContentProvider;
@@ -102,6 +108,10 @@ namespace Service::NFC {
102class NfcDevice; 108class NfcDevice;
103} // namespace Service::NFC 109} // namespace Service::NFC
104 110
111namespace Service::NFP {
112enum class CabinetMode : u8;
113} // namespace Service::NFP
114
105namespace Ui { 115namespace Ui {
106class MainWindow; 116class MainWindow;
107} 117}
@@ -319,6 +329,7 @@ private slots:
319 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 329 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
320 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 330 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
321 const std::string& game_path); 331 const std::string& game_path);
332 void OnGameListRemovePlayTimeData(u64 program_id);
322 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 333 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
323 void OnGameListVerifyIntegrity(const std::string& game_path); 334 void OnGameListVerifyIntegrity(const std::string& game_path);
324 void OnGameListCopyTID(u64 program_id); 335 void OnGameListCopyTID(u64 program_id);
@@ -365,6 +376,9 @@ private slots:
365 void ResetWindowSize720(); 376 void ResetWindowSize720();
366 void ResetWindowSize900(); 377 void ResetWindowSize900();
367 void ResetWindowSize1080(); 378 void ResetWindowSize1080();
379 void OnAlbum();
380 void OnCabinet(Service::NFP::CabinetMode mode);
381 void OnMiiEdit();
368 void OnCaptureScreenshot(); 382 void OnCaptureScreenshot();
369 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 383 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
370 void OnLanguageChanged(const QString& locale); 384 void OnLanguageChanged(const QString& locale);
@@ -383,10 +397,11 @@ private:
383 void RemoveVulkanDriverPipelineCache(u64 program_id); 397 void RemoveVulkanDriverPipelineCache(u64 program_id);
384 void RemoveAllTransferableShaderCaches(u64 program_id); 398 void RemoveAllTransferableShaderCaches(u64 program_id);
385 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 399 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
400 void RemovePlayTimeData(u64 program_id);
386 void RemoveCacheStorage(u64 program_id); 401 void RemoveCacheStorage(u64 program_id);
387 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 402 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
388 u64* selected_title_id, u8* selected_content_record_type); 403 u64* selected_title_id, u8* selected_content_record_type);
389 InstallResult InstallNSPXCI(const QString& filename); 404 InstallResult InstallNSP(const QString& filename);
390 InstallResult InstallNCA(const QString& filename); 405 InstallResult InstallNCA(const QString& filename);
391 void MigrateConfigFiles(); 406 void MigrateConfigFiles();
392 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 407 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -409,7 +424,13 @@ private:
409 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 424 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
410 bool CheckDarkMode(); 425 bool CheckDarkMode();
411 bool CheckSystemArchiveDecryption(); 426 bool CheckSystemArchiveDecryption();
427 bool CheckFirmwarePresence();
412 void ConfigureFilesystemProvider(const std::string& filepath); 428 void ConfigureFilesystemProvider(const std::string& filepath);
429 /**
430 * Open (or not) the right confirm dialog based on current setting and game exit lock
431 * @returns true if the player confirmed or the settings do no require it
432 */
433 bool ConfirmShutdownGame();
413 434
414 QString GetTasStateDescription() const; 435 QString GetTasStateDescription() const;
415 bool CreateShortcut(const std::string& shortcut_path, const std::string& title, 436 bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -417,10 +438,22 @@ private:
417 const std::string& command, const std::string& arguments, 438 const std::string& command, const std::string& arguments,
418 const std::string& categories, const std::string& keywords); 439 const std::string& categories, const std::string& keywords);
419 440
441 /**
442 * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
443 * The only difference is that it returns a boolean.
444 *
445 * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
446 */
447 bool question(QWidget* parent, const QString& title, const QString& text,
448 QMessageBox::StandardButtons buttons =
449 QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
450 QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
451
420 std::unique_ptr<Ui::MainWindow> ui; 452 std::unique_ptr<Ui::MainWindow> ui;
421 453
422 std::unique_ptr<Core::System> system; 454 std::unique_ptr<Core::System> system;
423 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; 455 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
456 std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
424 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; 457 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
425 458
426 MultiplayerState* multiplayer_state = nullptr; 459 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..b62ff620c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -16,7 +16,9 @@
16#include "common/settings_enums.h" 16#include "common/settings_enums.h"
17 17
18using Settings::Category; 18using Settings::Category;
19using Settings::ConfirmStop;
19using Settings::Setting; 20using Settings::Setting;
21using Settings::SwitchableSetting;
20 22
21#ifndef CANNOT_EXPLICITLY_INSTANTIATE 23#ifndef CANNOT_EXPLICITLY_INSTANTIATE
22namespace Settings { 24namespace Settings {
@@ -94,6 +96,15 @@ struct Values {
94 Setting<bool> confirm_before_closing{ 96 Setting<bool> confirm_before_closing{
95 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, 97 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
96 true, true}; 98 true, true};
99
100 SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
101 ConfirmStop::Ask_Always,
102 "confirmStop",
103 Category::UiGeneral,
104 Settings::Specialization::Default,
105 true,
106 true};
107
97 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; 108 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
98 Setting<bool> pause_when_in_background{linkage, 109 Setting<bool> pause_when_in_background{linkage,
99 false, 110 false,
@@ -103,7 +114,7 @@ struct Values {
103 true, 114 true,
104 true}; 115 true};
105 Setting<bool> mute_when_in_background{ 116 Setting<bool> mute_when_in_background{
106 linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default, 117 linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
107 true, true}; 118 true, true};
108 Setting<bool> hide_mouse{ 119 Setting<bool> hide_mouse{
109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, 120 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +194,9 @@ struct Values {
183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; 194 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; 195 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
185 196
197 // Play time
198 Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
199
186 bool configuration_applied; 200 bool configuration_applied;
187 bool reset_to_defaults; 201 bool reset_to_defaults;
188 bool shortcut_already_warned{false}; 202 bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..f2854c8ec 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,101 @@ 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 const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
68 constexpr int bytes_per_pixel = 4;
69
70 const IconDir icon_dir{
71 .id_reserved = 0,
72 .id_type = 1,
73 .id_count = static_cast<WORD>(scale_sizes.size()),
74 };
75
76 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
77 Common::FS::FileType::BinaryFile);
78 if (!icon_file.IsOpen()) {
79 return false;
80 }
81
82 if (!icon_file.Write(icon_dir)) {
83 return false;
84 }
85
86 std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
87 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
88 const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
89 const IconDirEntry icon_entry{
90 .width = static_cast<BYTE>(scale_sizes[i]),
91 .height = static_cast<BYTE>(scale_sizes[i]),
92 .color_count = 0,
93 .reserved = 0,
94 .planes = 1,
95 .bit_count = bytes_per_pixel * 8,
96 .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
97 .image_offset = static_cast<DWORD>(image_offset),
98 };
99 image_offset += icon_entry.bytes_in_res;
100 if (!icon_file.Write(icon_entry)) {
101 return false;
102 }
103 }
104
105 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
106 const QImage scaled_image = source_image.scaled(
107 scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
108 const BITMAPINFOHEADER info_header{
109 .biSize = sizeof(BITMAPINFOHEADER),
110 .biWidth = scaled_image.width(),
111 .biHeight = scaled_image.height() * 2,
112 .biPlanes = 1,
113 .biBitCount = bytes_per_pixel * 8,
114 .biCompression = BI_RGB,
115 .biSizeImage{},
116 .biXPelsPerMeter{},
117 .biYPelsPerMeter{},
118 .biClrUsed{},
119 .biClrImportant{},
120 };
121
122 if (!icon_file.Write(info_header)) {
123 return false;
124 }
125
126 for (int y = 0; y < scaled_image.height(); y++) {
127 const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
128 std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
129 std::memcpy(line_data.data(), line, line_data.size());
130 if (!icon_file.Write(line_data)) {
131 return false;
132 }
133 }
134 }
135 icon_file.Close();
136
137 return true;
138#else
139 return false;
140#endif
141}
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);